Containerized PostgreSQL with rootless Podman

Tags: #container,#linux,#database

Reading time: ~9min


This post is about running rootless Podman containers with PostgreSQL as a central example 📦️

The post serves as an optional preparation for the next post about interacting with databases with SQLx in Rust 🦀

Landscape mode recommended on mobile devices

Disclaimer

To restrict the scope of the post, I will assume that you know what a (rootless) Linux container is.

I will not cover Docker since there are many Docker tutorials in the internet. Just search for docker postgres in your favorite search engine 🔎 Nevertheless, almost all of the presented Podman commands work with Docker by replacing the binary podman with docker at the beginning of the command.

This post is not about SQL. Therefore, the details of SQL statements will not be explained.

Installation

Before starting with Podman containers, follow the official installation guide to install Podman on your system.

Note

Podman is preinstalled on Fedora Linux 😉

PostgreSQL container

To pull and run the PostgreSQL container, run the following command in your favorite shell:

podman run \
  -it \
  --rm \
  --name test-db \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=CHANGE_ME \
  docker.io/library/postgres:15

Let's break it into parts:

Note

The command doesn't need sudo in contrast to Docker! This is why we are talking about rootless Podman containers which offer better security than rootful containers.

After running the command, you will see PostgreSQL starting. You can connect to the default database postgres on localhost:5432 with the default user postgres and the entered password.

⚠️ Warning ⚠️

To stop the container, press CTRL+C. You will loose all the data in the database because we didn't configure persistent data storage yet!

Persistent data storage

To keep the data in the database even after stopping the container, we have to mount a volume.

To do so, create the directory data on the host system and add the following option to the podman run command: -v ./data:/var/lib/postgresql/data:Z.

-v /src/path:/dest/path is an option that mounts a volume so that the container can read and persistently write into /dest/path which is accessible by the host system in /src/path.

Note

You can use the option -v more than once.

:Z is a SELinux flag that is needed if your host system has SELinux enabled (like in Fedora). It tells the system that this volume will not be shared with other containers.

Note

If you do want to share a volume with other containers, you can use :z (lowercase) instead, but this is not a good idea for a database system.

How did I know about using /var/lib/postgresql/data as the destination directory?

Every container image has its own volumes documented in the image's documentation. For the used PostgreSQL image, the image's documentation can be found here.

Interacting with the database

Run the container again with a mounted volume from the section before.

Let's say we want to test that our data is now persistent. How can we interact with the database quickly?

To do so, open a second terminal tab and run the following command (while the container is still running):

podman exec -it test-db psql -U postgres

After running this command, PostgreSQL will be waiting for our input:

psql (15.2 (Debian 15.2-1.pgdg110+1))
Type "help" for help.

postgres=#

Let's create a table called notes with a self incrementing id as the primary key and a text note:

CREATE TABLE IF NOT EXISTS notes (id SERIAL PRIMARY KEY, note TEXT NOT NULL);

Now, verify that the table notes was created by listing all relations with \dt:

postgres=# \dt
         List of relations
 Schema | Name  | Type  |  Owner
--------+-------+-------+----------
 public | notes | table | postgres

Let's insert a row to our new table:

INSERT INTO notes (note) VALUES ('Testing containerized PostgreSQL');

Now, view all rows:

SELECT * FROM notes;

The output should be the following:

 id |               note
----+----------------------------------
  1 | Testing containerized PostgreSQL

Our database works 🎉 But does it store the data persistently?

Press CTRL+D to exit psql. Go back to the first terminal tab and stop the container with CTRL+C.

Now that the container is stopped, we want to verify that our data is stored persistently.

Run the podman run command again with the volume option. Go to the second terminal tab and run the podman exec command again and enter the following:

SELECT * FROM notes;

If you see your note "Testing containerized PostgreSQL", then your data was persistently stored 🎉

Running in the background

If you want to run a container in the background, replace -it with -d which stands for "detach". This way, you don't need to keep a terminal tab opened.

To verify that the container is running, run the command podman ps. It's output should look like the following:

CONTAINER ID  IMAGE                          COMMAND     CREATED        STATUS        PORTS                   NAMES
74c9ea0ac068  docker.io/library/postgres:15  postgres    2 minutes ago  Up 2 minutes  0.0.0.0:5432->5432/tcp  test-db

To stop a container running in the background, run podman stop CONTAINER_NAME.

If you want to see the logs of a container running in the background, run the command podman logs CONTAINER_NAME.

A container running in the background will not be running after a reboot! To automatically start a container after a reboot, we need a systemd service.

Systemd service

⚠️ Warning ⚠️

podman generate systemd is now deprecated! You should use Quadlet instead. I have a blog post about it: Quadlet: Running Podman containers under systemd

Automatically starting a container after a system reboot is especially important if you want to run the container on a server.

This can be achieved with a systemd service that Podman can generate for us.

Before generating a service file, we have to create a container with podman create instead of podman run:

podman create \
  --name test-db \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=CHANGE_ME \
  -v /home/USERNAME/volumes/test-db:/var/lib/postgresql/data:Z \
  docker.io/library/postgres:15

podman create creates a container without starting it.

The differences to our podman run command are that we removed the options -it and --rm and used an absolute path for our volume's source path.

If you run podman ps, then you should not find the container with the name test-db because it is not running.

The option -a shows all created containers (not only the ones running). The output of podman ps -a should look like the following:

CONTAINER ID  IMAGE                          COMMAND     CREATED         STATUS      PORTS                   NAMES
889f7f2c1c23  docker.io/library/postgres:15  postgres    25 seconds ago  Created     0.0.0.0:5432->5432/tcp  test-db

Now, you can generate the systemd service file with the following command:

podman generate systemd test-db -fn --new

After running the command, you will find a file named container-test-db.service in your current directory.

Move this service file to the user's configuration directory of systemd (create the directory if it doesn't already exist):

mv container-test-db.service ~/.config/systemd/user

Now, enable the service:

systemctl --user enable --now container-test-db.service

This service will start the container after every reboot. --now also starts the container now instead of waiting for the next reboot.

To see the status of the container service, run systemctl --user status container-test-db.

Since we use user services for systemd, we have to enable the linger for our user to start the containers without the user being logged in:

loginctl enable-linger

⚠️ Warning ⚠️

Enabling the linger is required for the container to be automatically started after a server reboot!

To disable the automatic restart of the container later, run systemctl --user disable --now container-test-db.

Configuration

Container images usually use environment variables for configuration. This is also the case for the PostgreSQL image.

The only required configuration variable is POSTGRES_PASSWORD which we already used. But there are other configuration variables that can be found in the documentation of the container image.

Closing words

I hope that you found this short introduction to rootless Podman helpful.

Now that we know how to run a PostgreSQL container, we can connect to such a database with SQLx in Rust. This will be the topic of the next post 😃

You can suggest improvements on the website's repository

Content license: CC BY-NC-SA 4.0