iTranslated by AI
Running PostgreSQL Containers with systemd Using Podman Quadlet
Introduction
By using Quadlet, which has been available since Podman v4.6, you can easily start containers with systemd. Previously, you could generate unit files using the podman generate systemd command, but Quadlet makes it even easier to manage containers under systemd.
For details on the benefits, please refer to the Red Hat Enterprise Linux 9 documentation: "15.4. Advantages of using Quadlets over the podman generate systemd command".
Preparation
Creating a Directory for Unit Files
Create a directory to place the Quadlet files.
mkdir -p $HOME/.config/containers/systemd
If XDG_CONFIG_HOME is set, use that instead.
mkdir -p $XDG_CONFIG_HOME/containers/systemd
Creating a Directory for Volumes
To persist PostgreSQL data, bind the data inside the container to the host-side filesystem. For example, create a directory at a location like this:
sudo mkdir -p /opt/data/podman/systemd/postgresql-server
sudo chown ${USER}: /opt/data/podman/systemd/postgresql-server
Of course, creating it under your home directory is also perfectly fine.
Creating Unit Files
Creating the .container File
Create a unit file named postgresql-server.container inside the unit file directory.
[Unit]
Description=PostgreSQL server container
After=local-fs.target
[Container]
Image=docker.io/library/postgres:18
Environment=POSTGRES_PASSWORD=postgrespwd
Environment=PGDATA=/var/lib/pgsql
Volume=/opt/data/podman/systemd/postgresql-server/pgsql:/var/lib/pgsql:Z
PublishPort=5432:5432
[Install]
WantedBy=default.target
In the [Container] section, you describe settings related to the container.
In this example, we use the official PostgreSQL image from Docker Hub. This image uses the POSTGRES_PASSWORD environment variable to set the default superuser password during the initial startup, so the password is hardcoded for the first run.
The default PGDATA for the docker.io/library/postgres:18 image is /var/lib/postgresql/data, but in this case, we set a different directory and bind it to the host-side filesystem.
Use the PublishPort= directive to expose ports to the host. The format is host:container.
Other environment variables can also be set as needed.
Generating the .service File
Generate the postgresql-server.service file from the postgresql-server.container file.
systemctl --user daemon-reload
To confirm that it has been placed under systemd management, let's check the status of postgresql-server.service.
systemctl --user status postgresql-server.service
○ postgresql-server.service - PostgreSQL server container
Loaded: loaded (/home/lillno/.config/containers/systemd/postgresql-server.container; generated)
Drop-In: /usr/lib/systemd/user/service.d
└─10-timeout-abort.conf
Active: inactive (dead)
The service is still inactive because it hasn't been started yet, but you can confirm that it is managed by systemd.
Starting the Service
Optional: Pulling the Image
The docker.io/library/postgres:18 image is pulled during the first startup, but you can also pull it in advance.
podman pull docker.io/library/postgres:18
Starting the postgresql-server Service
Start the postgresql-server service.
systemctl --user start postgresql-server.service
Starting PostgreSQL on OS Boot
Verify the connection using psql(1). The default username is postgres (this can be configured with the POSTGRES_USER environment variable during initial startup).
psql --host=localhost --username=postgres --password
Once the connection is confirmed, enable the postgresql-server.service.
When using the Quadlet mechanism, the .service file is a temporary artifact, so you cannot use the systemctl command to enable it. Although you cannot use enable, Quadlet sets it to a state equivalent to being enabled at the time of systemctl daemon-reload.
On the other hand, since systemctl is run with --user, the service starts upon user login and stops upon logout. To start the service at OS boot, use loginctl(1).
loginctl enable-linger ${USER}
Removing the Password from the .container File
The postgresql-server.container file created earlier had a hardcoded password, but since it is only used during the database initialization (initdb) process at the first startup, we will remove it from the file to improve security.
[Unit]
Description=PostgreSQL server container
After=local-fs.target
[Container]
Image=docker.io/library/postgres:18
Environment=PGDATA=/var/lib/pgsql
Volume=/opt/data/podman/systemd/postgresql-server/pgsql:/var/lib/pgsql:Z
PublishPort=5432:5432
[Install]
WantedBy=default.target
Afterwards, have systemd reload the files and restart the service.
systemctl --user daemon-reload
systemctl --user restart postgresql-server.service
Bonus
Permissions for the Bound Directory
Since it is running in Rootless Podman, you do not have permissions to access the directory bound to the host filesystem directly. This is the correct behavior and reflects the secure state of Rootless Podman.
ls /opt/data/podman/systemd/postgresql-server/pgsql
ls: cannot open directory '/opt/data/podman/systemd/postgresql-server/pgsql': Permission denied
If you want to perform operations with host-side general user permissions, use Podman's unshare.
podman unshare ls /opt/data/podman/systemd/postgresql-server/pgsql
PG_VERSION pg_hba.conf pg_replslot pg_subtrans postgresql.auto.conf
base pg_ident.conf pg_serial pg_tblspc postgresql.conf
global pg_logical pg_snapshots pg_twophase postmaster.opts
pg_commit_ts pg_multixact pg_stat pg_wal postmaster.pid
pg_dynshmem pg_notify pg_stat_tmp pg_xact
Handling Sensitive Information like Passwords in Podman
In this case, since the password was only needed temporarily, we hardcoded it directly, but this is not very desirable from a security perspective.
Podman has a feature called Secrets. If you need to use sensitive information such as passwords or API keys permanently, consider adopting the Secrets feature.
Discussion