| # Deploying Go servers with Docker | |
| 26 Sep 2014 | |
| Summary: How to use Docker's new official base images for Go. | |
| Andrew Gerrand | |
| ## Introduction | |
| This week Docker [announced](https://blog.docker.com/2014/09/docker-hub-official-repos-announcing-language-stacks/) | |
| official base images for Go and other major languages, | |
| giving programmers a trusted and easy way to build containers for their Go programs. | |
| In this article we'll walk through a recipe for creating a Docker container for | |
| a simple Go web application and deploying that container to Google Compute Engine. | |
| If you're not familiar with Docker, you should read | |
| [Understanding Docker](https://docs.docker.com/engine/understanding-docker/) | |
| before reading on. | |
| ## The demo app | |
| For our demonstration we will use the | |
| [outyet](https://godoc.org/github.com/golang/example/outyet) program from the | |
| [Go examples repository](https://github.com/golang/example), | |
| a simple web server that reports whether the next version of Go has been released | |
| (designed to power sites like [isgo1point4.outyet.org](http://isgo1point4.outyet.org/)). | |
| It has no dependencies outside the standard library and requires no additional | |
| data files at run time; for a web server, it's about as simple as it gets. | |
| Use "go get" to fetch and install outyet in your | |
| [workspace](https://golang.org/doc/code.html#Workspaces): | |
| $ go get github.com/golang/example/outyet | |
| ## Write a Dockerfile | |
| Replace a file named `Dockerfile` in the `outyet` directory with the following contents: | |
| # Start from a Debian image with the latest version of Go installed | |
| # and a workspace (GOPATH) configured at /go. | |
| FROM golang | |
| # Copy the local package files to the container's workspace. | |
| ADD . /go/src/github.com/golang/example/outyet | |
| # Build the outyet command inside the container. | |
| # (You may fetch or manage dependencies here, | |
| # either manually or with a tool like "godep".) | |
| RUN go install github.com/golang/example/outyet | |
| # Run the outyet command by default when the container starts. | |
| ENTRYPOINT /go/bin/outyet | |
| # Document that the service listens on port 8080. | |
| EXPOSE 8080 | |
| This `Dockerfile` specifies how to construct a container that runs `outyet`, | |
| starting with the basic dependencies (a Debian system with Go installed; | |
| the [official `golang` docker image](https://registry.hub.docker.com/_/golang/)), | |
| adding the `outyet` package source, building it, and then finally running it. | |
| The `ADD`, `RUN`, and `ENTRYPOINT` steps are common tasks for any Go project. | |
| To simplify this, there is an | |
| [`onbuild` variant](https://github.com/docker-library/golang/blob/9ff2ccca569f9525b023080540f1bb55f6b59d7f/1.3.1/onbuild/Dockerfile) | |
| of the `golang` image that automatically copies the package source, fetches the | |
| application dependencies, builds the program, and configures it to run on | |
| startup. | |
| With the `onbuild` variant, the `Dockerfile` is much simpler: | |
| FROM golang:onbuild | |
| EXPOSE 8080 | |
| ## Build and run the image | |
| Invoke Docker from the `outyet` package directory to build an image using the `Dockerfile`: | |
| $ docker build -t outyet . | |
| This will fetch the `golang` base image from Docker Hub, copy the package source | |
| to it, build the package inside it, and tag the resulting image as `outyet`. | |
| To run a container from the resulting image: | |
| $ docker run --publish 6060:8080 --name test --rm outyet | |
| The `--publish` flag tells docker to publish the container's port `8080` on the | |
| external port `6060`. | |
| The `--name` flag gives our container a predictable name to make it easier to work with. | |
| The `--rm` flag tells docker to remove the container image when the outyet server exits. | |
| With the container running, open `http://localhost:6060/` in a web browser and | |
| you should see something like this: | |
| .image docker/outyet.png | |
| (If your docker daemon is running on another machine (or in a virtual machine), | |
| you should replace `localhost` with the address of that machine. If you're | |
| using [boot2docker](http://boot2docker.io/) on OS X or Windows you can find | |
| that address with `boot2docker ip`.) | |
| Now that we've verified that the image works, shut down the running container | |
| from another terminal window: | |
| $ docker stop test | |
| ## Create a repository on Docker Hub | |
| [Docker Hub](https://hub.docker.com/), the container registry from which we | |
| pulled the `golang` image earlier, offers a feature called | |
| [Automated Builds](http://docs.docker.com/docker-hub/builds/) that builds | |
| images from a GitHub or BitBucket repository. | |
| By committing [the Dockerfile](https://github.com/golang/example/blob/master/outyet/Dockerfile) | |
| to the repository and creating an | |
| [automated build](https://registry.hub.docker.com/u/adg1/outyet/) | |
| for it, anyone with Docker installed can download and run our image with a | |
| single command. (We will see the utility of this in the next section.) | |
| To set up an Automated Build, commit the Dockerfile to your repo on | |
| [GitHub](https://github.com/) or [BitBucket](https://bitbucket.org/), | |
| create an account on Docker Hub, and follow the instructions for | |
| [creating an Automated Build](http://docs.docker.com/docker-hub/builds/). | |
| When you're done, you can run your container using the name of the automated build: | |
| $ docker run goexample/outyet | |
| (Replace `goexample/outyet` with the name of the automated build you created.) | |
| ## Deploy the container to Google Compute Engine | |
| Google provides | |
| [container-optimized Google Compute Engine images](https://developers.google.com/compute/docs/containers/container_vms) | |
| that make it easy to spin up a virtual machine running an arbitrary Docker container. | |
| On startup, a program running on the instance reads a configuration file that | |
| specifies which container to run, fetches the container image, and runs it. | |
| Create a [containers.yaml](https://cloud.google.com/compute/docs/containers/container_vms#container_manifest) | |
| file that specifies the docker image to run and the ports to expose: | |
| version: v1beta2 | |
| containers: | |
| - name: outyet | |
| image: goexample/outyet | |
| ports: | |
| - name: http | |
| hostPort: 80 | |
| containerPort: 8080 | |
| (Note that we're publishing the container's port `8080` as external port `80`, | |
| the default port for serving HTTP traffic. And, again, you should replace | |
| `goexample/outyet` with the name of your Automated Build.) | |
| Use the [gcloud tool](https://cloud.google.com/sdk/#Quick_Start) | |
| to create a VM instance running the container: | |
| $ gcloud compute instances create outyet \ | |
| --image container-vm-v20140925 \ | |
| --image-project google-containers \ | |
| --metadata-from-file google-container-manifest=containers.yaml \ | |
| --tags http-server \ | |
| --zone us-central1-a \ | |
| --machine-type f1-micro | |
| The first argument (`outyet`) specifies the instance name, a convenient label | |
| for administrative purposes. | |
| The `--image` and `--image-project` flags specify the special | |
| container-optimized system image to use (copy these flags verbatim). | |
| The `--metadata-from-file` flag supplies your `containers.yaml` file to the VM. | |
| The `--tags` flag tags your VM instance as an HTTP server, adjusting the | |
| firewall to expose port 80 on the public network interface. | |
| The `--zone` and `--machine-type` flags specify the zone in which to run the VM | |
| and the type of machine to run. (To see a list of machine types and the zones, | |
| run `gcloud compute machine-types list`.) | |
| Once this has completed, the gcloud command should print some information about | |
| the instance. In the output, locate the `networkInterfaces` section to find the | |
| instance's external IP address. Within a couple of minutes you should be able | |
| to access that IP with your web browser and see the "Has Go 1.4 been released | |
| yet?" page. | |
| (To see what's happening on the new VM instance you can ssh into it with | |
| `gcloud compute ssh outyet`. From there, try `sudo docker ps` to see which | |
| Docker containers are running.) | |
| ## Learn more | |
| This is just the tip of the iceberg—there's a lot more you can do with Go, Docker, and Google Compute Engine. | |
| To learn more about Docker, see their [extensive documentation](https://docs.docker.com/). | |
| To learn more about Docker and Go, see the [official `golang` Docker Hub repository](https://registry.hub.docker.com/_/golang/) and Kelsey Hightower's [Optimizing Docker Images for Static Go Binaries](https://medium.com/@kelseyhightower/optimizing-docker-images-for-static-binaries-b5696e26eb07). | |
| To learn more about Docker and [Google Compute Engine](http://cloud.google.com/compute), | |
| see the [Container-optimized VMs page](https://cloud.google.com/compute/docs/containers/container_vms) | |
| and the [google/docker-registry Docker Hub repository](https://registry.hub.docker.com/u/google/docker-registry/). |