Create a Private PostgreSQL Database for Your Development Environment in Seconds
By: Doron Chosnek
Tags: Postgres, Programming, Database, Docker, Containers

Build the Dev Stack

The following docker-compose.yml file defines a PostgreSQL container running a specific version of PostgreSQL and pgAdmin 4 (the most common administrative tool for Postgres). The contents of this file warrant a deeper explanation.

The Docker Compose file structure

This file defines two “services” to be created: Postgres and pgAdmin. Each service consists of a single container pulled for you from Docker Hub. Postgres and pgAdmin will be available at port 5432 and port 8080 respectively. Point any programs you write at hostname localhost and browse to http://localhost:8080 to access pgAdmin.

Keep reading for instructions on how to point pgAdmin to Postgres.

PostgreSQL version

In the line that defines the Postgres container, you’ll want to specify the exact version of Postgres that you need. The version is represented as a tag where 9.6.12-alpine is the version in our example. Check here to see which other versions are available.

Storage for Postgres

The docker-compose.yml file above specifies two volume mappings for Postgres. These two mappings will make directories on your computer accessible to Postgres.

The folder mapped to /docker-entrypoint-initdb.d contains the SQL files that Postgres will use to initialize the database. Put SQL files and shell scripts you need in that directory and they will be automatically executed in alphabetical order. The folder mapped to /var/lib/postgresql/data contains the persistent storage for the actual database.

When Postgres starts, it follows the simple flowchart below. If there is no data in the DB, then it executes every SQL file and shell script (in alphabetical order) in the folder on your computer that is mapped to in the /docker-entrypoint-initdb.d directory. If there is data in the folder on your computer mapped to /var/lib/postgresql/data , then it ignores the files.

Should you mount both directories? It depends. The table below describes the behavior you should expect by mapping each of the two volumes for Postgres.

My advice is to map both directories. You can remove files from the init directory if you no longer want to initialize your DB. You can also delete the data directory from your computer to delete any data that would have persisted (Docker will recreate an empty folder in its place the next time you run docker-compose up ).

Pro Tip: you can include CSV files in the init folder on your computer and then populate tables with data from those CSV files by adding the appropriate command to an SQL file in the init directory.

CREATE TABLE Employee(id, first_name, last_name, salary);

COPY Employee FROM '/docker-entrypoint-initdb.d/emp.csv'

WITH (FORMAT CSV, HEADER);

Storage for pgAdmin

Although pgAdmin is just a tool for viewing and configuring the database, its connection(s) to the database must be configured. This can be done through the GUI with the “add server” command. Note that the hostname is the container_name parameter from our YML file: some-postgres . The password is also specified in the YML file: mysecret .

An alternative is to save a lot of clicking and typing by specifying these settings (everything except the password) in a JSON file. Map that JSON file to /pgadmin4/servers.json on the container (line 22 in our example YML file) to avoid having to manually configure pgAdmin’s connection to Postgres.

The settings file can specify multiple connections between pgAdmin and Postgres (to connect as different users or to connect to different databases). The following example has only one connection.

Networking

The container_name parameter is just a name, but it is the name that will be used by pgAdmin to access the database on port 5432. The reason is illustrated in this diagram. The two containers are connected to each other via a private network and can therefore access each other via their hostnames (the container names) some-postgres and some-pgadmin . The host (your computer and web browser), however, only sees ports 5432 and 8080 exposed and therefore accesses them at localhost:5432 and localhost:8080 .

The name of that internal network can be specified in the compose file, but there is no value in naming a network whose name is never referenced by your code. Let Docker handle that! If you are curious, you can always view your private and temporary network’s name. In the snippet below, I ran Docker Compose from desktop, so it named the network desktop_default .