Local Development with Go, PostgreSQL and Elasticsearch in Docker
I tend to use Docker containers more and more over Vagrant boxes for my small (and not-so-small) projects or experiments. It’s not breaking news that containerization has a lot of advantages over virtualization, especially when you are working on multiple projects on the same machine, or on projects with a service-oriented architecture.
One of the things I love the most with using Docker for local development is that instead of setting up every service used by my application, I can just use prebuilt images, configure and run them quickly. This is made even easier with Docker compose, since you can quickly setup, build, run or destroy your entire environment with just a few configuration and commands.
Let’s begin by creating a small and straightforward HTTP server with Go:
$ mkdir app
$ cd app
$ touch main.go
The Dockerfile needed to build the image is straightforward:
We can now create a basic docker-compose.yml
file listing the single service app
. We also mount the host working directory as the directory containing the code to run in the container, so we can make changes to the code without having to rebuild everything.
Run docker-compose up
to build the image and launch the service container. You should be able to request the page at http://localhost:8080 and get the response from the Go application.
We now have a basic HTTP server running in Docker, great. Let’s say we also need PostgreSQL and Elasticsearch to run the application. We’ll add two services to the docker-compose.yml
file using the existing postgres
image from the official Docker repository and the elasticsearch
image provided by Elastic.
We can use environment variables to customize the setup of the Postgres server, providing the DB name, user and password to use. We also expose the different ports needed to access these services.
It is likely that you will want to control the order in which containers are launched, to ensure that the Postgres server is running before the Go server starts and tries to connect to it. You can use the wait-for-it.sh script for that. This is basically a wrapper that will wait for a specific TCP address to respond before launching the given command. We can use it to wrap the go run
command so it can wait for the db service before executing it.
$ wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh
$ chmod +x wait-for-it.sh
Finally, update the docker-compose.yml
file to wrap the go run main.go
command:
You can now run your service containers with docker-compose up -d
. If you ever need to rebuild your entire environment from scratch, you can use docker-compose down --rmi all
and then restart.
It can be cumbersome to manually restart the app container each time you make changes to the code. I like to use watchexec to automatically call docker-compose restart
when files are being modified.
$ brew install watchexec
$ docker-compose up -d
$ watchexec --restart --exts "go" --watch . "docker-compose restart app"
Here we are, with a development environment ready, in only a matter of minutes. That’s what I like with using Docker and compose, I can just focus on application development and not care about long setup of the different services needed for my code to run locally.
Need to use Redis tomorrow to do some caching? Well, just add another service. Redis also has its official Docker image.
And there you go, link this service to your app service, and you have a Redis instance ready to use, listening at redis:6379
.
Happy coding!