In this part of my Learning Docker series, we are going to look at databases and see how they can be used with Docker. The goal is to create a development environment that can be used with our Sinatra app.
You can find the complete source code on GitHub:
Almost any decent application nowadays has the demand for some persistentdata storage - be it for content, session information or configuration. This is also true for the service we developed in the last posts. In this specific case, we need to store the content of the application in a persistent manner.
For this, we want to use a database and make it available to the application. While there are different strategies how you could do this, putting the database in a Docker container and adding it to our orchestration is the most sensible approach. This allows us to take full advantage of docker-compose and its support for multi- container applications, and makes it increadibly easy to start the development environment on another machine.
Choosing a Database
For this project, we want to use PostgreSQL as our database management system. Before going into the details on how to create the Docker container for the database, let me quickly reason why I only want to use this container for development.
When we deploy the application to production, certain needs arise with regards to the data stored within the database. For example, it must be persistent and available. While this sounds easy enough, preventing and dealing with data losses and failures is an art in itself. Essentially, you need backups to protect against data loss, and you need replicasto ensure availability.
While you could take care of this yourself, and many people do, I prefer to outsource this to specialists who know what they are doing. I love building applications, so my knowledge of database maintenance is adequate at best, and quite frankly I don’t like it. With a hosted service like Amazon RDS (for example), I get a highly available database management system that is maintained by professionals and that takes care of almost everything out-of-the-box.
Getting the Database
Thanks to Docker Hub, this step is incredibly easy. I bet that whatever database you want to use, an image already exists on Docker Hub. So go there and search for the database management system you want to use. For PostgreSQL, you probably want to use the offical image.
db: image: "postgres"
While this would be all that docker-compose needs to know to download, build and start the image, looking at the image’s documentation reveals that we can start it it with some environment variables to automatically create a database if none exists already. So let’s add them do
db: image: "postgres" environment: - POSTGRES_USER=sinatra - POSTGRES_PASSWORD=sinatra
POSTGRES_PASSWORD are optional and specify a database and a user that get created during initialization. This is done only if no database exists, so you don’t have to worry about overwriting your existing data.
To be able to access the database via the command line utility psql or with other tools, it is useful to expose the database’s port to the host. This is done by simply adding a new
ports directive to
db: image: "postgres" environment: - POSTGRES_USER=sinatra - POSTGRES_PASSWORD=sinatra ports: - "5432:5432"
When you run
docker-compose up now, you should see a whole bunch of output coming from both a
api_1 and a
db_1 service. Using psql, you can inspect the database with the following command (change IP to
localhost on Linux):
$ psql -h 192.168.99.100 -U sinatra sinatra
This connects to the database sinatra (last argument) as the user sinatra on the host 192.168.99.100 (aka docker-machine).
Head over there and come back once you set your application up.
After preparing Sinatra, there is one thing we still want to do. If you followed the instructions, this step is not required, but still a good practice. In the file
config/database.yml, we configured our app to make use of some environment variables that are not available to the container yet. So let’s add some lines to
api: build: . ports: - "4567:80" links: - "db" environment: - DATABASE_HOST=db - DATABASE_NAME=sinatra - DATABASE_USER=sinatra - DATABASE_PASSWORD=sinatra
environment section sets the environment variables our app uses to discover the database and connect to it. Having these in place makes it easy to make changes to the configuration, e.g. changing the database user.
Linking app and database
The last step for us to do is to link our application container with the database container. While you could use the port and the IP address of the docker-machine, it is better to use the dynamic linking feature of docker-compose. Just imagine that a colleague wants to check out the project, but he’s running Linux, while you are running OS X and have configured to application to connect to 192.168.99.100. The application will break for your colleague, since Dockerruns natively on his machine and makes no use of docker-machine.
To set up linking with docker-compose, just open up its configuration in
docker-compose.yml and add the directive
links to the app:
api: build: . ports: - "4567:80" links: - "db"
That’s it. Your app is now magically linked to your database. Ok, not magically, but it’s pretty close. docker-compose takes care of that for you.
Since our application now has a database, you can start playing around with it, and create records either via the API or with psql.
While this was all pretty straightforward and easy, there is one thing to keep in mind:
If we were to rebuild the database container, all our data would be gone. It is only stored within the container.
For a development environment, data persistency is not that important, and with our database setup, it is unlikely that we need to recreate the container regularly or maybe even at all. So I decided to go with the much simpler approach, and accept the limitations it has. But your mileage my vary.
Whenever you need persistency within a Docker container, you need to add a volumeto the container. This is outside the scope of this post, though, so I encourage you to check out the documention on volumes if you have the need for data persistency in your container.
You can take the same approach to add even more compenents to your app if you need to. There are a bunch of images on Docker Hub, and a lot of articles around on how to go totally crazy with docker-compose.
If you have any questions, please share them in the comments below. The same goes for mistakes you find or if you know a better approach. You can find the source code for this project on GitHub: firstname.lastname@example.org
See you in the next post!