How to Dockerize a Database

What you'll get out of this article:

  • Learn the benefits of Dockerizing an application
  • Understand how Docker Compose works and when you should use it
  • Create a containerized PostgreSQL database

This article is for you if:

  • You have a general idea of what Docker does
  • You aren't that familiar with Docker Compose (or just need a refresher)

The power of Docker really clicked for me once I started applying it to my own projects. No more long hours googling for all the dependencies I needed to run my apps. I could spin up any project from scratch with 100% confidence that it would run as expected, even if I was on another computer.

Today we're going to create a "dockerized" PostgreSQL database — that is, a database that has been containerized with Docker. We're choosing PostgreSQL because it's a solid all-around relational database, as well as one of the most popular.

Why Do You Need to Know This?

If you're generally familiar with Docker and its benefits, feel free to skip to the first method for dockerizing a database.

You might be wondering why you would want to dockerize a database. Why not just spin up a local instance, or create one in the cloud using AWS's RDS service or another managed database service like ElephantSQL.

If you're deploying a project to production, I can all but guarantee that you won't use Docker for your database. Instead, you'll likely use a cloud-based solution like AWS's RDS (Relational Database Service). It's more scaleable, and you'll be able to focus on your app, not database management.

Hold on - if we don't need to dockerize our databases in production, then why are we even talking about this topic?

Because dockerizing your database is an incredibly useful step in development.

If you're starting to develop an app, or if you just want to practice SQL in an authentic environment, using Docker to build your database is a great solution.

Don't believe me? Let's compare it to a few other solutions.

  • Dockerized DB vs. AWS RDS
    • Running a local database with Docker is free forever.
    • AWS will charge you once you pass the limits of their free tier.
  • Dockerized DB vs. free cloud solution (e.g. ElephantSQL)
    • You can customize your local dockerized database to run almost any version of almost any commonly used database.
    • Cloud providers offer little to no customization over your database and often offer just one type of database.
  • Dockerized DB vs. non-dockerized database on your local machine
    • By dockerizing your database, you'll be able to duplicate those exact settings on any other machine. You can also instantly boot up an identical database on your machine at any time.
    • A non-containerized local database gives you none of these benefits.

Let's learn two different ways you can spin up a Docker container running a Postgres database.

Reminder: Before running any Docker commands, make sure Docker Desktop (or your preferred Docker software) is installed and running on your machine!


Spin up a Database With Docker Compose

For this lesson, we'll use Docker Compose, since it's what you'll often use to integrate a database into a Dockerized app.

If you're unfamiliar with Docker Compose, you can check out this handy overview in the Docker docs, or just read my quick explanation below.

What's Docker Compose?

Docker Compose is a tool used to define and run multi-container applications. It's configured through a docker-compose.yml file. Plain old Docker, on the other hand, uses a Dockerfile. But how exactly does Docker Compose differ from regular Docker?

A Dockerfile simply defines the environment for your app. For instance, if you have a Node.js app that uses a PostgreSQL database, your Dockerfile would define the environment for the Node server — but not the database. In production, you typically won't keep your database inside a container.

However, in development, you won't always want to connect to a scalable database in the cloud. This is particularly true if you're developing a proof of concept, or if you're working on a smaller-scale project like an API for a personal site. In cases like these, you'll supplement your Dockerfile with a docker-compose.yml that defines the architecture of your project.

Going back to our above example, if your Dockerfile already specifies the setup instructions for a Node server, you could create a Docker Compose file to do the following :

  • Specify how to build the Node server
  • Add instructions for building a database
  • Associate the server with the database

Here's an example of a Django API I built when I was originally planning my blog. Inside the docker-compose.yml I define how to set up the Django API as well as an additional Postgres database. Feel free to check it out. (I didn't end up using it, but the functionality is sound!)

Creating our database

Let's get started!

Since we only want to build a database, and not an app to connect it to, we're going to skip the Dockerfile and go straight to Docker Compose.

That's right - all we need is a single docker-compose.yml file, where we define the database we want Docker Compose to spin up.

Inside an empty folder, create a file called docker-compose.yml and type the following:

# docker-compose.yml

# Version 3 is currently the newest version of the Compose file format
version: '3'

# We're creating a single 'service': our database.
services:
  # Our service doesn't need to be called postgres.
  # It can be called anything you want, as long as the image is correct
  postgres:
    # Here we define a prebuilt Postgres image,
    # i.e. the version we want to use
    image: 'postgres:13.2-alpine'
    # Map port 5432 of the container to port 5432 on our machine,
    # so we'll be able to access it at `localhost:5432`
    ports:
      - '5432:5432'
    # Define our username and password - they can be whatever you want.
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=pass
    # Volumes persist data generated and used by Docker containers
    # They're optional but useful
    volumes:
      # We name our volume "db-data" and define a path for it
      - db-data:/var/lib/postgresql/data:delegated

# Since we included a volume for our db,
# we need to list it here!
volumes:
  db-data:

Great, our Docker Compose file is all ready to go. Let's use it to build a database.

Open your terminal, cd into the directory with your docker-compose.yml file, and run the following command:

docker-compose up -d

(You can omit the -d flag if you want. It just tells the container to run in "detatched" mode, or in the background.)

Our database is running! The last step is to interact with it.

Feel free to use a visual database tool like DBeaver or pgAdmin to work with your database. You could also interact directly with the container using the docker exec command.

I'm going to use the terminal command psql, which lets me interact with Postgres databases directly through the command line.

psql -h localhost -p 5432 -U postgres

With this command we tell psql that we want to connect to the Postgres database located at port 5432 on our machine (localhost), and that we're logging in as the user named postgres (or whatever name you specified in your Docker Compose file).

Next you'll probably see the following prompt:

Password for user postgres:

Enter the password you specified in your file. And we're in!

psql (13.1, server 13.2)
Type "help" for help.

postgres=#

I won't walk you through how to build your database since this isn't a SQL lesson, and since you're reading a lesson on how to Dockerize your database, there's a good chance that you already know how to do this!

Reviewing our Docker Compose file.

There's a bit to digest here, especially if this is your first time working with Docker Compose.

However, the Docker Compose format is quite straightforward once you've internalized its terminology.

The only parts of this file that aren't absolutely necessary are the two volumes fields:

# docker-compose.yml

version: '3'

services:
  postgres:
    # ......
    volumes:
      - db-data:/var/lib/postgresql/data:delegated

volumes:
  db-data:

By including them, we allocate some space inside the host system (i.e. your machine) to store data that will be used by the container. This will let us persist data, like a table full of usernames and passwords, even after you stop your container and start again. While not essential, it's definitely convenient if you don't want to repopulate your data everytime you boot up your database!

Note: you could absolutely use a Dockerfile to set up a database, particularly if none of the images available on Docker Hub suits your needs. There's a tutorial in the Docker docs that shows you how to do just that. However, the postgres:13.2-alpine image suits our needs, so there's

docker exec -it 05b3a3471f6f bash
root@05b3a3471f6f:/# psql -U postgres
postgres-# CREATE DATABASE mytest;
postgres-# \q

Bonus: One-line Docker Command

We've just spent a while going over how to deliberately set up a database using a Docker Compose file. But what if you want a quick and dirty way to spin up a database in a Docker container?

docker run --name postgres -d -p 5432:5432 -v db-data:/var/lib/postgresql/data:delegated postgres:13.2-alpine

If you read closely, you'll notice that we're including essentially all the information we used in our docker-compose.yml file. We aren't including environmental variables, but we could by using the -e flag, e.g. -e POSTGRES_PASSWORD=pass.

Let's take this a step further and use our terminal to access the container's own command line. We're going to use the docker exec command, which I very briefly mentioned in the previous section.

To access the container's command line, you'll need the container's ID. This is the long string of letters and numbers you see in the terminal immediately after you run the command to start the container is the container's ID.

docker exec -it [CONTAINER ID] bash

If you ran this successfully, you should see something like this in response.

bash-5.1#

You're inside the container! Now just run one more command:

psql -U postgres

Now, the moment we've been waiting for:

psql (13.2)
Type "help" for help.

postgres=#

We're exactly where we were at the end of the previous section!

So why did we spend so much time going over the Docker Compose method if we could just use a one-liner?

Well, the biggest downside of squishing everything into a single docker run command is that we loose the ability to easily integrate it into a multi-container setup, which Docker Compose is built for.

If you want to practice running some SQL commands, this one-liner is probably enough. But if you're setting up a database that you want to connect to a project in development, Docker Compose is your friend.

Summary

In this post we've learned a couple of ways to dockerize a database:

  • Using Docker Compose
  • Starting a database through the terminal with docker run

In addition, we've learned why Docker Compose is such a great tool for developing apps that require outside dependencies like databases. If we're developing a Node server, we can use Docker Compose to containerize the server and also create a database for us to test the app with.

Next Steps

Now that you're familiar with how to run a PostgreSQL database with Docker, here are some suggestions for how to expand your knowledge.

Try Dockerizing a Different Database

What if you wanted to work with MySQL instead of Postgres? Or MongoDB? Try modifying your Docker Compose file to work for an image of a different database.

(Pro tip: Choose an alpine image, as these tend to be the most minimal and take up the smallest amount of space.)

Dockerize a Personal Project

Take an app that you've worked on, and use Docker Compose to containerize it. This app should contain multiple containers, or "services," such as...

  • A frontend (React, Vue, Angular, etc.) and backend (Node, Django, Rails, etc.)
  • A backend and a database (like our examples above)

This will require some more research into Dockerfiles and Docker Compose, but it will make you much more comfortable with Docker as a whole.