Setup Guide

Setting up PostgreSQL with Docker

Clone the source code of the article from setting-up-postgressql-with-docker-on-windows-10

Introduction

As part of any application development, we definitely need a database. There might be different use cases which need different databases. Having them all installed on our machine might not be an ideal scenario. In the world of containers, this would ease us to provision the choice of database easily for testing purpose without actually installing them.

In this article we shall provision PostgreSQL docker container and go through steps on using it.

Why use PostgreSQL?

PostgreSQL is a powerful, open source object-relational database system that uses and extends the SQL language combined with many features that safely store and scale the most complicated data workloads.

A fundamental characteristic of an object-relational database is support for user-defined objects and their behaviours including data types, functions, operators, domains and indexes. This makes PostgreSQL extremely flexible and robust. Among other things, complex data structures can be created, stored and retrieved.

PostgreSQL comes with many features aimed to help developers build applications, administrators to protect data integrity and build fault-tolerant environments, and help you manage your data no matter how big or small the dataset. In addition to being free and open source, PostgreSQL is highly extensible. For example, you can define your own data types, build out custom functions, even write code from different programming languages without recompiling your database.

What are we going to do?

In this article, we shall go through how we can use Docker to explore PostgreSQL. We shall run series of commands with Docker that is installed on Windows 10. Below are series of things we will be doing:

  • Create database
  • Connecting to the database from container shell
  • Connecting to the database from SQL Client
  • Import Schema & Data when provisioning new database
  • Stopping and Starting container
  • Perform database operations

Prerequisites

Having Docker installed on your machine is necessary to try out provisioning PostgreSQL container.

Install Docker on Windows 10 Home Edition

As Docker Desktop does not support Windows 10 Home Edition, we need to install Docker Toolbox for Windows. Follow the detailed steps provided here to get docker toolbox installed and configured.

If everything is installed and configured as suggested, running the below command should list the version of docker.

Docker Version
~:\> docker -v

Docker version 19.03.1, build 74b1e89e8a

Configure Port Forwarding Rule on the default VirtualBox image

If we need to connect to the provisioned database from SQL client installed on Windows 10, we need to configure port forwarding for the default VirtualBox image that is created as part of the Docker setup on Windows 10 Home Edition. Follow the steps to configure port forwarding for ssh & port 5432 as below:

  • Right Click on the default image and choose Settings

  • Choose Network > Adapter1 > expand Advanced > click Port Forwarding

  • Configure Port Forwarding rules as below

Configure VirtualBox Port forwarding

Provision PostgreSQL Container

Before provisioning PostgreSQL container, we need to create a data container with a mounted volume which will be used to store the database that we create. Execute the below command to create a data container.

Create Volume
~:/> docker create -v /article_postgres_docker --name PostgresData alpine

Executing the below command with docker run will pull the image and start the container with name pgdocker. PostgreSQL stores its data in /var/lib/postgresql/data, so we are mounting the created data container volume with --volume-from flag. Also as seen we are exposing port 5432 (the PostgreSQL default) and running the container in detached (-d) mode (background). Password to be used for the database is configured with the environment variable POSTGRES_PASSWORD.

Start Docker Container
~:\> docker run -p 5432:5432 --name pgdocker -e POSTGRES_PASSWORD=password -d --volumes-from PostgresData postgres

Options passed:

  • --detach, -d - Run container in background and print container ID
  • --publish, -p - Publish a container’s port(s) to the host
  • --name - Assign a name to the container
  • --volumes-from - Mount volumes from the specified container(s)
  • --env, -e - Set environment variables

Running docker ps -a should list all containers. docker ps just shows containers running and not shows the volume that is created.

List all containers
~:\> docker ps -a

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
1f5ae3781326        postgres            "docker-entrypoint.s…"   5 minutes ago       Up 5 minutes        0.0.0.0:5432->5432/tcp   pgdocker
64773150d120        alpine              "/bin/sh"                8 minutes ago       Created                                      PostgresData

Options passed:

  • --all, -a - Show all containers (default shows just running)

Create database

Run the below command with docker exec to create testdb database with which we can play around.

Create database
~:\> docker exec pgdocker psql -U postgres -c "create database testdb"

Connecting to the database from container shell

To start interacting with the database, we can use docker exec command to launch interactive shell running inside the container. Options -i allows us to make the container to wait for interaction from host but actual interaction from the console (terminal) is possible after we “allocate tty driver” with flag -t. This is an easy alternative to login to PostgreSQL and start exploring it.

Launch shell
~:\> docker exec -it pgdocker sh

Once logged into shell, access psql terminal and try out few commands like listing databases, create table, insert data etc.,

psql terminal
# psql -U postgres
psql (12.2 (Debian 12.2-2.pgdg100+1))
Type "help" for help.
List Databases
postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 testdb      | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
Display version
postgres=# select version();
                                                     version
------------------------------------------------------------------------------------------------------------------
 PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
(1 row)
Switch to testdb database
postgres=# \c testdb
Create Table
testdb=# CREATE TABLE tbl_keyvalue (id int, key varchar(255), value varchar(255));
CREATE TABLE
Insert data
testdb=# INSERT INTO tbl_keyvalue (id,key, value) VALUES (1, 'key1', 'value1');
INSERT 0 1
List tables
testdb=# \dt
-- Lists all tables in the current database.
            List of relations
 Schema |     Name     | Type  |  Owner
--------+--------------+-------+----------
 public | tbl_keyvalue | table | postgres
(1 row)

Connecting to the database from SQL Client

As we are exposing the default TCP port 5432 from provisioned PostgreSQL container, we can connect to it using any standard SQL client. For this article we shall use HeidiSQL, which lets us to connect to most of the database systems MariaDB, MySQL, Microsoft SQL or PostgreSQL.

Install HeidiSQL and start the application. This should open Session manager. Configure database connectivity details and open the session.

HeidiSQL Session Manager

This should open up the application with tables listed under public section in the connected database.

HeidiSQL database explorer

Stopping and Starting container

As we mounted data container volume to persist PostgreSQL data, we will not loose data even after stopping and starting the database.

Execute the below commands to stop the container and start the container

List running containers
~:\> docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
9e4dab542448        postgres            "docker-entrypoint.s…"   9 hours ago         Up 9 hours          0.0.0.0:5432->5432/tcp   pgdocker
d29dc7a359c8        alpine              "/bin/sh"                9 hours ago         Created                                      PostgresData
Stop Container
~:\> docker stop 9e4dab542448
Start Container
~:\> docker start 9e4dab542448
Launch psql terminal
~:\> docker exec -it pgdocker sh

# psql -U postgres testdb

testdb=# \dt
            List of relations
 Schema |     Name     | Type  |  Owner
--------+--------------+-------+----------
 public | tbl_keyvalue | table | postgres
(1 row)

Discarding the container

Execute the below commands to stop and remove database container and its associated volume.

Stop container
~:\> docker stop pgdocker
Remove Containers
~:\> docker rm pgdocker PostgresData

Import Schema & Data when provisioning new database

Generally whenever we are working on database for testing, we need to provision it with some base schema and test data. To try this out, we shall use PostgreSQL sample database that is available from one of the Github projects - Pagila which is a port of the Sakila example database available for MySQL.

Clone github repository to get the schema and data files to tryout importing them to the provisioned PostgreSQL container.

To bootstrap the database with cloned sql files, we need to create shell script which can be executed upon startup. Create bootstrap.sh file as below which will create database with the provided DB_NAME environment variable and import the schema and initial data using psql.

bootstrap.sh
#!/bin/bash
set -e

POSTGRES="psql --username ${POSTGRES_USER}"

echo "Creating database: ${DB_NAME}"

$POSTGRES <<EOSQL
CREATE DATABASE ${DB_NAME} OWNER ${POSTGRES_USER};
EOSQL

echo "Creating schema..."
psql -d ${DB_NAME} -a -U${POSTGRES_USER} -f /pagila-schema.sql

echo "Populating database initial data"
psql -d ${DB_NAME} -a  -U${POSTGRES_USER} -f /pagila-insert-data.sql

echo "Populating database..."
psql -d ${DB_NAME} -a  -U${POSTGRES_USER} -f /pagila-data.sql

Create Dockefile as below to build the custom image. As observed, we are copying individual sql files to the image.

Dockerfile
FROM postgres:11

# Custom initialization scripts
COPY bootstrap.sh /docker-entrypoint-initdb.d/bootstrap.sh
COPY pagila-schema.sql /pagila-schema.sql
COPY pagila-insert-data.sql /pagila-insert-data.sql
COPY pagila-data.sql /pagila-data.sql

RUN chmod +x /docker-entrypoint-initdb.d/bootstrap.sh && sed -i -e 's/\r$//' /docker-entrypoint-initdb.d/bootstrap.sh

/bin/bash^M: bad interpreter: No such file or directory issue will be thrown when bootstrap.sh file is created on windows and executed in docker container. We need to include sed command in RUN section in dockerfile to replace the line ending characters before executing bootstrap.sh file upon startup.

Execute the below command to create custom docker image with name postgre-pagila-db.

~:\> docker build -t postgre-pagila-db .

Before starting the container, lets create a data container with a mounted volume which will be used to store the database that we create. Execute the below command to create data container.

Create Volume
~:\> docker create -v /article_postgres_docker --name PostgresData alpine

Executing the below command with docker run will use the custom image and start the container with name pgdocker-pagila-db. PostgreSQL stores its data in /var/lib/postgresql/data, so we are mounting the created data container volume with --volume-from flag. Also as seen we are exposing port 5432 (the PostgreSQL default) and running the container in detached (-d) mode (background). Database name and Password to be used for the database is configured with DB_NAME and POSTGRES_PASSWORD environment variable.

Start Docker Container
~:\> docker run -p 5432:5432 --name pgdocker -e DB_NAME=pagila -e POSTGRES_PASSWORD=password -d --volumes-from PostgresData postgre-pagila-db

Launch shell and start exploring the database created with schema and data using below commands.

Launch shell and explore Pagila database
-- Launch shell
~:\> docker exec -it pgdocker sh

-- connect to psql terminal
# psql -U postgres
psql (12.2 (Debian 12.2-2.pgdg100+1))
Type "help" for help.

-- List databases
postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 pagila    | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
(4 rows)

-- Switch to pagila database
postgres=# \c pagila
You are now connected to database "pagila" as user "postgres".

-- List tables
pagila=# \dt
              List of relations
 Schema |       Name       | Type  |  Owner
--------+------------------+-------+----------
 public | actor            | table | postgres
 public | address          | table | postgres
 public | category         | table | postgres
 public | city             | table | postgres
 public | country          | table | postgres
 public | customer         | table | postgres
 public | film             | table | postgres
 public | film_actor       | table | postgres
 public | film_category    | table | postgres
 public | inventory        | table | postgres
 public | language         | table | postgres
 public | payment          | table | postgres
 public | payment_p2017_01 | table | postgres
 public | payment_p2017_02 | table | postgres
 public | payment_p2017_03 | table | postgres
 public | payment_p2017_04 | table | postgres
 public | payment_p2017_05 | table | postgres
 public | payment_p2017_06 | table | postgres
 public | rental           | table | postgres
 public | staff            | table | postgres
 public | store            | table | postgres
(21 rows)

-- Clear screen
pagila=# \! clear

Perform database operations

Let’s perform some database operations on the provisioned database with schema and data.

Perform database operations

-- list 5 actors
pagila=# select * from actor limit 5;
 actor_id | first_name |  last_name   |      last_update
----------+------------+--------------+------------------------
        1 | PENELOPE   | GUINESS      | 2017-02-15 09:34:33+00
        2 | NICK       | WAHLBERG     | 2017-02-15 09:34:33+00
        3 | ED         | CHASE        | 2017-02-15 09:34:33+00
        4 | JENNIFER   | DAVIS        | 2017-02-15 09:34:33+00
        5 | JOHNNY     | LOLLOBRIGIDA | 2017-02-15 09:34:33+00
(5 rows)

-- update an actor first_name
pagila=# update actor set first_name='EDWARD' where first_name = 'ED';
UPDATE 3

-- Verify if record is updated
pagila=# select * from actor where actor_id=3;
 actor_id | first_name | last_name |          last_update
----------+------------+-----------+-------------------------------
        3 | EDWARD     | CHASE     | 2020-03-09 01:42:44.975053+00
(1 row)

Conclusion

This article is more of a little hack on making our application development faster by quickly setting up database of our choice without worrying on annoying steps on installing and configuring the database. As PostgreSQL is one of the more common used databases, having it provisioned with Docker and connecting to our local application is quite simple and easy.

Clone the source code of the article from setting-up-postgressql-with-docker-on-windows-10

Madan Narra13 Posts

Software developer, Consultant & Architect

Madan is a software developer, writer, and ex-failed-startup co-founder. He has over 10+ years of experience building scalable and distributed systems using Java, JavaScript, Node.js. He writes about software design and architecture best practices with Java and is especially passionate about Microservices, API Development, Distributed Applications and Frontend Technologies.

  • Github
  • Linkedin
  • Facebook
  • Twitter
  • Instagram

Contents

Get The Best Of All Hands Delivered To Your Inbox

Subscribe to our newsletter and stay updated.