Set up a PostgreSQL database with Docker

Having a ready to use database instance that every programmer can quickly run on their machine can save a lot of time. In this article you will learn how to set up a PostgreSQL database in a Docker container.

What we are going to build

I want to configure the container in a way that it is easy to reuse. Therefore, I’m going to enclose environment variables in a separate file. Thanks to this you can run exactly the same database as mine or replace the PostgreSQL version, as well as credentials with different values. You won’t need to change the docker-compose.yml file.

Usage

You can learn how to use this database in a Spring Boot application in the Add a PostgreSQL database to your Spring Boot project post.

You can find the example project used in this post in the spring-boot-postgres-flyway repository.

Requirements

  • Docker runs on many platforms. I work on Ubuntu 18.04, you can check your version with the following command:

Learn how to install Docker from the official documentation or take a look at the How to install development tools on Ubuntu with a single bash command post to get the bash script that installs the tool automatically.

Service configuration

Compose allows us to specify default environment variables. As a result we can avoid keeping container properties hardcoded in the docker-compose.yml file. This will simplify reusing the compose setup and keep it more clean.

We are going to split our service configuration into two files: docker-compose.yml and .env. They ought to be placed in the same directory. In my case, I keep them in the main folder of my example project – spring-boot-postgres-flyway.

docker-compose

Take a look at the file below and notice how the PostgreSQL image version, credentials and database name are set with the environment variables. Copy this file to your project directory:

Service options explained

Let’s take a look at the properties configured in the docker-compose.yml file.

image

The postgres service is going to be built from an official PostgreSQL Docker image. You can find all available tags on the image page. The image description contains also the list of all variables than can be specified for the service.

ports

You map container ports to the ports on the host. In my example I used the short syntax. You can find details in the compose docs:

Either specify both ports (HOST:CONTAINER), or just the container port (an ephemeral host port is chosen).

https://docs.docker.com/compose/compose-file/#ports

I also wrote the port mapping in a string as it’s recommended to avoid errors in case using container port lower than 60:

YAML parses numbers in the format xx:yy as a base-60 value. For this reason, we recommend always explicitly specifying your port mappings as strings.

https://docs.docker.com/compose/compose-file/#ports
volumes

To designate a directory that will be used to keep the database content on my machine I need the volumes option. Docker will create and manage a directory on the host file system (/var/lib/docker/volumes/ on Linux) and use it as the source for the /var/lib/postgresql/data on the container.

Line 9 contains the name of the volume and the path where the directory is mounted in the container. All named volumes have to be declared in the root level of the docker-compose.yml file, hence in the end I put the following lines:

environment

We define all environment variables that will be used by the container. You can provide only keys for those values that you want to keep secret, as is stated in the docs:

Environment variables with only a key are resolved to their values on the machine Compose is running on, which can be helpful for secret or host-specific values.

https://docs.docker.com/compose/compose-file/#environment
restart

I didn’t establish any restart config nor restart policy in my example. I want to launch this container manually, only when I’m working on this project. The default restart policy is no, and this service won’t be restored automatically in case of a failure or when I reboot my computer. You can specify this property according to your needs.

Define the environment variables

I’m going to define the environment variables that are used in the Compose file. You can learn more about this in the Docker documentation on variable substitution. Create the following .env file with the variables that you want to pass to the container:

Variables explained

We can create custom variables or define values for those that are available for the image we are using.

POSTGRES_VERSION

This is my custom variable. My project uses the postgres:9.6-alpine image, but you can use any other image that suits you. As I said earlier, you will find the available tags on the official image page on dockerhub.

POSTGRES_PASSWORD

Caveat: If you execute connections to the database only from inside the same container you don’t need the password at all, as you can find in the docs:

The PostgreSQL image sets up trust authentication locally so you may notice a password is not required when connecting from localhost (inside the same container). However, a password will be required if connecting from a different host/container.

https://hub.docker.com/_/postgres
POSTGRES_USER

The username specified in this variable will be used as the database name by default. I preferred to name my database with my project name, therefore I added the POSTGRES_DB variable. In the documentation you can also read:

This variable will create the specified user with superuser power and a database with the same name. If it is not specified, then the default user of postgres will be used.

https://hub.docker.com/_/postgres
POSTGRES_DB

Use this variable if you want to determine the name of your database. Otherwise it will be identified with the user name or the postgres string. You can read in the documentation:

This optional environment variable can be used to define a different name for the default database that is created when the image is first started. If it is not specified, then the value of POSTGRES_USER will be used.

https://hub.docker.com/_/postgres
COMPOSE_PROJECT_NAME

As you can see in the line 6, I added the COMPOSE_PROJECT_NAME variable to hold my project name. This will result in appending the string I specified here to all service and volume names as a prefix. By default this variable holds the basename of the directory which keeps your docker-compose.yml file.

If you don’t keep the docker configuration in the root directory of your project, provide here a descriptive label, so the container name will look like yourawesomeproject_postgres_1 and the volume name like yourawesomeproject_postgres.

Overriding variables

You can put your own variables in this file or override the defaults. Any value that is set in your shell environment will be used instead of the one placed in the .env file.

The priority of sources from which the values will be applied:

  1. Compose file
  2. Shell environment variables
  3. Environment file
  4. Dockerfile
  5. Variable is not defined

For example, I can export a new value for the COMPOSE_PROJECT_NAME variable:

And when I start the container, testname is the string used as the prefix:

Verify the final config

To print your resolved config to the terminal, run the following command in the directory with your docker-compose.yml file:

You will find the options for this command in its official documentation. It will print the resultant configuration that is going to be applied to your container. For my example I got the following output:

Run the container

The container specification is ready, you can run the following command in your terminal:

Now you can verify whether the container has been started successfully:

You can inspect the volume that has been created for the container:

The following command:

should produce an output similar to this:

The work done in this section is contained in the commit 2e18da0816f94ad4ae080a76f90704265f077da5.

Why I should use Docker services in my dev environment

You may not have a choice if you need to adapt to the requirements of many projects that are run on your machine. However, we shouldn’t give the cold shoulder to the containerization even if we deal with a single project.

Lack of interference

You can easily run many apps and you don’t need to worry that they will tamper with the environment on your machine. Furthermore, errors are reproducible across many machines. All developers in a team run services in identical environment. Bugs can be easily recreated because there are no environment factors that could interfere with application.

Agility

You can create, pause and destroy services without breaking a sweat. It is possible to switch quickly between multiple projects even if they have conflicting dependencies. What’s more, you can test multiple configurations by simply editing the docker-compose.yml or .env files.

Reusability

Due to keeping the default environment variables in the .env file, compose configuration is less coupled with a specific project.

Lowering the entry curve

A well prepared docker-compose.yml file should be kept in a project repository with the rest of the code. This way, every new team member can set up the app environment within minutes and start pushing commits the very first day of their work.

Photo by  Francis Daniel on StockSnap

Leave a Reply

Your email address will not be published. Required fields are marked *