Managed Service Podman
Podman is a daemonless container engine for developing, managing, and running OCI Containers on your Linux System. Containers can be run on our managed servers in rootless mode.
Since the syntax is mostly identical to Docker, you can add the following alias for easier use:
alias docker=podman
Please keep in mind that the Podman syntax can change with newer versions and will no longer be identical to Docker eventually.
The full documentation of the Podman project can be found here:
https://podman.readthedocs.io/en/latest/index.html
Cleanup Jobs
By default, we automatically create two cleanup jobs during the installation of Podman. One is running in the night from Sunday to Monday and will remove all unused Images. The second one is running every night to the 1st of each month and will remove all unused volumes.
These are safety measures to keep the footprint of Podman as minimal as possible and reduce the risk to overfill your disk space.
If you want to change these cleanup jobs, just write us a ticket and we will adapt it to your needs.
Searching, pulling & listing images
Search for images on remote registries with keywords:
podman search <search_term>
Enhance your search results with filters:
podman search ghost --filter=is-official
Pull the image that you would like to have locally:
podman pull docker.io/library/ghost
List all the images present on your environment:
podman images
Podman searches in different registries. It's recommended to use the full image name (e.g. docker.io/library/ghost
instead of ghost
) to ensure, that you are using the correct image.
Running a container
We run a sample Ghost container that serves the easy-to-use Ghost CMS.
podman run -dt -p 8080:2368/tcp docker.io/library/ghost
This container starts in detached mode -d
. This means you will get a container ID after the container has been started. With the option -t
, a pseudo-tty will be added to run arbitrary commands in an interactive shell.
With the -p 8080:2368/tcp
option, we use port forwarding to be able to access the webserver of Ghost running on port 2368 through the TCP port 8080 on the host system.
Show running containers
podman ps -a
gives us an overview of created and running containers.
$ podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5728ad900bc4 docker.io/library/ghost:latest node current/inde... 4 hours ago Up 4 hours ago 0.0.0.0:8080->2368/tcp gifted_edison
Attach to a running container
With the CONTAINER ID
you are able to attach to an already running container. You can catch the ID in the podman ps
output.
$ podman attach b3376ff455a0
[2020-06-10 09:17:15] INFO "GET /" 200 512ms
Test the running container
The container is now reachable on the port 8080
on your host system.
As you may have noticed above in the Podman ps output, the container has no IP address assigned.
You can test with curl
if your Ghost container application is running correctly:
$ curl localhost:8080| grep "og:site"
<meta property="og:site_name" content="Ghost" />
Proxy the port using nine-manage-vhosts
If you already have the managed services Nginx or Apache2 running, you can simply use nine-manage-vhosts
to expose your application to the outside world using a Let's Encrypt enabled vhost.
Create the new vhost:
sudo nine-manage-vhosts virtual-host create testdomain.org --template=proxy --template-variable=PROXYPORT=8080
Register at let's encrypt:
sudo nine-manage-vhosts certificate register-client --contact-email=contact@yourcompany.org
Create and enable a new let's encrypt cert on the vhost:
sudo nine-manage-vhosts certificate create --virtual-host=testdomain.org
sudo nine-manage-vhosts virtual-host update testdomain.org --template=proxy_letsencrypt_https --template-variable=PROXYPORT=8080
Only needed with apache webserver: To automatically redirect from http to https with using a Let's Encrypt certificate, you can set the template proxy_letsencrypt_https_redirect
.
docker-compose
Starting with Ubuntu 22.04 Podman supports docker-compose
natively and your installation includes docker-compose
v1.
Install the latest v2 version of docker-compose
mkdir -p ~/bin
curl -L $(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r '.assets[] | select(.name == "docker-compose-linux-x86_64") | .browser_download_url') -o ~/bin/docker-compose
chmod +x ~/bin/docker-compose
If you are using Podman on Ubuntu 20.04, you can't use native docker-compose
. However, you can use podman-compose
, which replicates the behavior:
podman-compose
mkdir -p ~/bin
curl -o ~/bin/podman-compose https://raw.githubusercontent.com/containers/podman-compose/main/podman_compose.py
chmod +x ~/bin/podman-compose
ln -s ~/bin/podman-compose ~/bin/docker-compose # compatibility for the docker-compose command
Example Usage of docker-compose:
Next, we will run Ghost CMS in network mode slirp4netns
with a compose file. slirp4netns
is Podman's default network
driver and can be omitted in the compose file.
For this example, we use an already locally running MySQL database named nmd_ghost
.
Prepare your own docker-compose.yaml
File.
# by default, the Ghost image will use SQLite (and thus requires no separate database container)
# we have used MySQL here merely for demonstration purposes (especially environment-variable-based configuration)
services:
ghost:
image: ghost:3-alpine
restart: always
ports:
- 8080:2368
environment:
# see https://docs.ghost.org/docs/config#section-running-ghost-with-config-env-variables
database__client: mysql
database__connection__host: localhost
database__connection__user: nmd_ghost
database__connection__password: EeNae5xaoapoh5RoDah1muwu
database__connection__database: nmd_ghost
# this url value is just an example, and is likely wrong for your environment!
url: http://testdomain.org
The docker-compose.yaml
file can then be run
by the docker-compose
command:
docker-compose -f docker-compose.yml up
Generate Systemd user unit files
We recommend creating a Systemd user service so that the container starts automatically after a system reboot. Depending on the OS version, there are two different ways to create the user service. Although newer podman versions still support the old podman generate systemd
way for creating Systemd units, it's recommended to use Quadlets from Ubuntu Noble on as it generates much clearer and more controllable Systemd units.
Old way with podman generate (up to Ubuntu Jammy)
Generate the Systemd user unit files of the pod named examplepod
:
podman generate systemd --new --files --name examplepod
With podman ps
and podman pod ps
, you can see the names of your running pods to generate the correct Systemd unit files.
Copy the generated Systemd user unit files into your Systemd directory:
cp -pv /home/www-data/pod-examplepod.service /home/www-data/container-examplepod_ghost_1.service ~/.config/systemd/user/
Finally, enable the Systemd user processes:
systemctl --user enable container-examplepod_ghost_1.service
systemctl --user enable pod-examplepod.service
New way with Quadlets and Podlet (from Ubuntu Noble on)
Quadlet is a new format for creating Systemd startup files built into podman since version 4.4. A Quadlet file ends with .container
(or .image
, .volume
, .network
, .kube
) and is created in ~/.config/containers/systemd/
. It's quite similar to a Systemd unit file: All options and tables available in standard Systemd unit files (like [Service]
or [Install]
) are supported and passed to the generated Systemd unit.
Additionally, there's a special section (e.g. [Container]
for a .container
file) with options that are mainly used as parameters for the resulting podman run
command. The full documentation is available on the Ubuntu manpage.
To avoid having to create the Quadlet files yourself, there's the tool Podlet that generates ready-to-use Quadlet files from an existing object. From Ubuntu Noble on, Podlet is available on all servers with our Managed Podman Service.
In the following example, a Quadlet file for an existing container named examplecontainer
is being created:
podman run --detach --tty --name examplecontainer \
--env NODE_ENV=development \
--env database__connection__filename='/var/lib/ghost/content/data/ghost.db' \
--volume some-ghost-data:/home/www-data/ghost \
--publish 8080:2368/tcp \
docker.io/library/ghost
podlet --unit-directory --install --overwrite generate container examplecontainer
Wrote to file: /home/www-data/.config/containers/systemd/ghost.container
With podman ps
and podman pod ps
, you can see the names of your running pods to generate the correct Systemd unit files.
The Quadlet file is generated in ~/.config/containers/systemd/
:
cat ~/.config/containers/systemd/ghost.container
[Container]
Environment=NODE_ENV=development database__connection__filename=/var/lib/ghost/content/data/ghost.db
Image=docker.io/library/ghost
PodmanArgs=--tty
PublishPort=8080:2368/tcp
Volume=some-ghost-data:/home/www-data/ghost
[Install]
WantedBy=default.target
You can edit the file if you would like to add additional config options. Afterwards, you can generate a Systemd unit from the Quadlet:
systemctl --user daemon-reload
systemctl --user status ghost.service
The daemon-reload
does NOT create a separate Systemd unit file. Instead, the unit is created from the Quadlet on the fly with a Systemd generator. Because of that, the resulting service can't be enabled or disabled with systemctl --user enable/disable
. It's still autostarted after reboot because of the [Install]
section.
Start the Systemd service:
systemctl --user start ghost.service
systemctl --user status ghost.service
● ghost.service
Loaded: loaded (/home/www-data/.config/containers/systemd/ghost.container; generated)
Active: active (running) since Wed 2024-12-04 13:39:10 CET; 1s ago
Main PID: 540254 (conmon)
Tasks: 25 (limit: 3337)
Memory: 80.8M (peak: 80.8M)
CPU: 1.100s
CGroup: /user.slice/user-33.slice/user@33.service/app.slice/ghost.service
├─libpod-payload-805ff2ecbcaa737f9b969dd21323b0115fb3c46c7800ab88563663a1e41341b8
│ └─540256 node current/index.js
└─runtime
├─540239 /usr/bin/slirp4netns --disable-host-loopback --mtu=65520 --enable-sandbox --enable-seccomp --en>
├─540241 rootlessport
├─540246 rootlessport-child
└─540254 /usr/bin/conmon --api-version 1 -c 805ff2ecbcaa737f9b969dd21323b0115fb3c46c7800ab88563663a1e413>
Podlet can also create Quadlets from Podman commands or compose files.
You can find a full documentation for Podlet here.
Networking
Modes (Host, Bridged)
In Bridged
(default) mode, all containers in the same Podman pod
are sharing the same network namespace.
Therefore, the containers will share the same IP, MAC address and port mappings.
Between the containers in one pod
, you can always communicate using localhost
.
There exists another mode called Host
, which can be specified to podman using the network=host
parameter.
If you use the Host
network mode for a container, that container's network stack is not isolated from the Podman Host (the container shares the host's networking namespace), and the container does not get its own IP address allocated. With the Host
mode, it's possible to connect to a local MySQL daemon running on a managed server or to connect to other TCP ports exposed on the host system.
Rootless
As we are running all our containers rootless, the network is set up automatically.
Port Publishing
Only so-called "high ports" can be published with rootless containers. All ports below 1024 are privileged and cannot be used for publishing.
Instead of publishing port 80, we need to switch to a higher port. In this example we will use our Ghost container, which is running on port 2368, and publish it on TCP port 8080 on localhost:
podman run -dt -p 8080:2368/tcp docker.io/library/ghost
You can use podman -P
to automatically publish and map ports.
Check the published and occupied ports:
$ podman port -a
c0194f22266c 2368/tcp -> 0.0.0.0:8080
Container to Container communication
Communicating between two rootless containers can be achieved in multiple ways.
The easiest way is to use the published ports and the underlying host.
Check for listening containers:
podman ps
Show published ports and the own host IP:
podman port <container_id>
ip a
Run a new container to contact your host IP with the published port:
podman run -it --rm fedora curl <HOST_IP_ADDRESS>:8080
Volumes: Mount Storage to Container
To make data persistent, you'd either need to save it to an external system like a database or you mount local storage using the -v
volumes flag. This will allow you to use two different mounting methods:
- Bind Mounts
- Named Volumes
Bind Mounts
Bind Mounts are created by mounting a file or directory inside the container.
Bind mounted volumes containging files and folders with subuids and subgids can be deleted with the following command:
podman unshare rm -r ${bind_mount_dir}
Named Volumes
Named Volumes are managed by Podman and can be changed with it's CLI. They are stored in a specific directory in ~/.local/share/containers/storage/volumes/
.
Backup
All volumes data is automatically backed up on a managed servers. But a separate backup is probably necessary because of the following reasons:
- You can't restore specific data out of volumes.
- The data is not persisted in a consistent state (for example database storage).
That's why we'd recommend to create separate dumps of the data. Those dumps then get backed up automatically by our managed backup.