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:
1 2 |
$ lsb_release -d Description: Ubuntu 18.04.4 LTS |
- Docker Engine – Community with Docker CLI. While working on this project I was using the following version:
1 2 |
$ docker --version Docker version 19.03.6 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# spring-boot-postgres-flyway/docker-compose.yml version: "3.3" services: postgres: image: "postgres:${POSTGRES_VERSION}" ports: - "5432:5432" volumes: - postgres:/var/lib/postgresql/data environment: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} volumes: postgres: |
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 (
https://docs.docker.com/compose/compose-file/#portsHOST:CONTAINER
), or just the container port (an ephemeral host port is chosen).
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
https://docs.docker.com/compose/compose-file/#portsxx:yy
as a base-60 value. For this reason, we recommend always explicitly specifying your port mappings as strings.
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:
1 2 3 4 |
# spring-boot-postgres-flyway/docker-compose.yml … volumes: postgres: |
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:
1 2 3 4 5 6 |
# spring-boot-postgres-flyway/.env POSTGRES_VERSION=9.6-alpine POSTGRES_PASSWORD=admin POSTGRES_USER=db POSTGRES_DB=springbootpostgresflyway COMPOSE_PROJECT_NAME=springbootpostgresflyway |
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
https://hub.docker.com/_/postgrestrust
authentication locally so you may notice a password is not required when connecting fromlocalhost
(inside the same container). However, a password will be required if connecting from a different host/container.
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
https://hub.docker.com/_/postgrespostgres
will be used.
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
https://hub.docker.com/_/postgresPOSTGRES_USER
will be used.
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:
- Compose file
- Shell environment variables
- Environment file
- Dockerfile
- Variable is not defined
For example, I can export a new value for the COMPOSE_PROJECT_NAME variable:
1 |
$ export COMPOSE_PROJECT_NAME=testname |
And when I start the container, testname
is the string used as the prefix:
1 2 3 4 5 |
$ docker-compose up -d Creating network "testname_default" with the default driver Creating volume "testname_postgres" with default driver Creating testname_postgres_1 ... Creating testname_postgres_1 ... done |
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:
1 |
$ docker-compose config |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
services: postgres: environment: POSTGRES_DB: springbootpostgresflyway POSTGRES_PASSWORD: admin POSTGRES_USER: db image: postgres:9.6-alpine ports: - published: 5432 target: 5432 volumes: - postgres:/var/lib/postgresql/data:rw version: '3.3' volumes: postgres: {} |
Run the container
The container specification is ready, you can run the following command in your terminal:
1 |
$ docker-compose up -d |
Now you can verify whether the container has been started successfully:
1 2 3 |
$ docker ps IMAGE COMMAND CREATED STATUS PORTS NAMES postgres:9.6-alpine "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:5432->5432/tcp springbootpostgresflyway_postgres_1 |
You can inspect the volume that has been created for the container:
1 2 3 |
$ docker volume ls DRIVER VOLUME NAME local springbootpostgresflyway_postgres |
The following command:
1 |
docker volume inspect springbootpostgresflyway_postgres |
should produce an output similar to this:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "CreatedAt": "2020-03-09T18:43:20+01:00", "Driver": "local", "Labels": { "com.docker.compose.project": "springbootpostgresflyway", "com.docker.compose.volume": "postgres" }, "Mountpoint": "/var/lib/docker/volumes/springbootpostgresflyway_postgres/_data", "Name": "springbootpostgresflyway_postgres", "Options": null, "Scope": "local" } |
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