Freezing and Thawing Droplets in a DigitalOcean

April 21, 2014

DigitalOcean has been a game-changer for me.  Why create another space-hungry VM locally when you can spin up a new machine in 60 seconds on DO?  And it gets better: Tugboat.  Now, I can manage my droplets from the command line.  Since, I typically use (and reuse) droplets like I did local VMs, I often want to set a droplet aside for a while (maybe weeks or months) and return to it later.  Fortunately, DO provides a way to put snapshots into cold storage and then retrieve them later.  But freezing and thawing droplets wasn’t easy enough.  I made a suggestion to DO, but I doubt it’ll be implemented anytime soon (if ever), so I used Tugboat and some Groovy scripts to roll my own.

Here is what I was looking for:

Freezing a droplet

Thawing a droplet

The goal:

$ # Assume we have a droplet foo
$ tugboat create foo
$ # Imagine you're done working with foo for now
$ freeze foo
$ # Foo is a snapshot & the droplet is destroyed.
$ # ... weeks pass and you have a hankering for foo ...
$ thaw foo
$ # Foo is back, Baby!

While it would be great to have freeze & thaw button on the DO website freeze & thaw parameters for Tugboat, I didn’t have the time to make a pull request for Tugboat… so here are the scripts:

~/bin/freeze

This script will snapshot a droplet, replacing any snapshot of the same name, and destroy the droplet.

#!/usr/bin/env groovy

class TugboatException {
	// Our very own little exception is born. It's a buoy!
}

def getImageInfo = {
	"tugboat images".execute().text.split("\n").find{
		it.startsWith(imageName+" ")
	}
}

def waitFor = { imageName, to='appear' -> /* to='appear' or 'disappear' */
	attempts = 0
	while (true) {
		attempts++
		imageInfo = "tugboat images".execute().text.split("\n").find{
			it.startsWith(imageName+" ")
		}
		if ((imageInfo && to=='appear') || (!imageInfo && to=='disappear')) break
		if (imageInfo || attempts > 20) {
			throw new TugboatException("$imageName did not $to within 3 min. Gave up.")
		}
		sleep(10000) // wait 10 seconds between checks
	}
}

def cmd = { description, command ->
	print description
	response = command.execute().text
	println "done."
}

def cli = new CliBuilder(usage:'freeze DROPLET_NAME')
cli.q(longOpt:'quiet', '')
def options = cli.parse(args)

if (!options.arguments() || options.arguments().size != 1) {
	cli.usage()
	System.exit(0)
}

imageName = options.arguments()[0]

if (! "tugboat droplets".execute().text ==~ /(?ims).*^$imageName\s.*/) {
	println "$imageName droplet does not exist"
	System.exit(1)
}

imageInfo = getImageInfo()
if (imageInfo) {
	imageId = (imageInfo =~ /id:\s*(\d+)/)[0][1]
	if (imageId) {
		cmd("Destroying old $imageName image...", "tugboat destroy-image -c -i $imageId")
		waitFor(imageName, 'disappear')
	}
}

cmd('Telling droplet to halt...', "tugboat halt $imageName")

cmd('Waiting for droplet to shut down...', "tugboat wait $imageName -s off")

sleep(3000)

cmd('Taking snapshot of droplet...', "tugboat snapshot $imageName $imageName")

print "Waiting for image to complete..."
waitFor(imageName)
println "done."

cmd("Destroying $imageName droplet...", "tugboat destroy -c $imageName")

~/bin/thaw

This script will restore a frozen droplet and start it up for you.

#!/usr/bin/env groovy

def getImageInfo = {
	"tugboat images".execute().text.split("\n").find{
		it.startsWith(imageName+" ")
	}
}

def cmd = { description, command ->
	print description
	response = command.execute().text
	println "done."
}

def cli = new CliBuilder(usage:'thaw IMAGE_NAME')
cli.q(longOpt:'quiet', '')
def options = cli.parse(args)

if (!options.arguments() || options.arguments().size != 1) {
	cli.usage()
	System.exit(0)
}

imageName = options.arguments()[0]

if ("tugboat droplets".execute().text ==~ /(?ims).*^$imageName\s.*/) {
	println "$imageName droplet already exists"
	System.exit(1)
}

imageInfo = getImageInfo()
if (!imageInfo) {
	println "Image $imageName not found"
	System.exit(2)
}

imageId = (imageInfo =~ /id:\s*(\d+)/)[0][1]
if (!imageId) {
	println "Unable to parse $imageName image id"
	System.exit(3)
}

print "Thawing image..."
response = "tugboat create $imageName -i $imageId".execute().text
println "done."

print "Waiting for droplet to start..."
response = "tugboat wait $imageName".execute().text
println "done."