Rootless Containers with podman

From [1]:

Podman is a daemonless, open source, Linux native tool designed to make it easy to find, run, build, share and deploy applications using Open Containers Initiative (OCI) Containers and Container Images. 

I use it to set up simple LAMP (linux/apache/mysql/php) servers for web development. I want to be able to run the containers without needing root. I want the containers to run on 64-bit x86 systems and on raspberry pi 5 systems (arm64 v8 architecture). All the examples below work on both architectures unless noted.

Installation (arch linux)

sudo pacman -S podman buildah

(during installation, it may state that there are 3 providers available for oci-runtime - crun, krun or runc - I chose crun based on https://wiki.archlinux.org/title/Podman#Runtimes).

During installation, optional dependencies for podman were listed:

    apparmor: for AppArmor support
    btrfs-progs: support btrfs backend devices
    fuse-overlayfs: for deprecated storage driver in rootless environment
    slirp4netns: for alternative rootless network support
    podman-compose: for docker-compose compatibility
    podman-docker: for Docker-compatible CLI

I have none installed (at the moment).

Simple example 1 (apache httpd server) (based on https://podman.io/docs#running-a-container)

Find an apache server image:

podman search docker.io/httpd --filter=is-official

Expect output like:

NAME                              DESCRIPTION
docker.io/library/httpd           The Apache HTTP Server Project

(will choose docker.io/library/httpd).

Create a container with this image:

podman run -d -p 127.0.0.1:8000:80 docker.io/library/httpd

The container will be stored under ~/.local/share/containers/ (but see https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md#user-configuration-files). See https://hub.docker.com/_/httpd for more details on httpd images.

The -d flag means run the container in the background and print the container ID. The -p 127.0.0.1:8000:80 maps tcp port 80 on the container (which is what the apache server listens on) to 8000 on the host (the localhost interface only). To map to all interfaces on the host (so the page can be accessed from another host), leave off the 127.0.0.1:.

Check it is running:

podman ps

with expected output like:

CONTAINER ID  IMAGE                           COMMAND           CREATED         STATUS         PORTS                 NAMES
a6aba01359ea  docker.io/library/httpd:latest  httpd-foreground  14 seconds ago  Up 15 seconds  0.0.0.0:8000->80/tcp  focused_dirac

(use podman ps -a to show all containers, including stopped ones).

Test the web server:

curl http://localhost:8000

with expected output:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>It works! Apache httpd</title>
</head>
<body>
<p>It works!</p>
</body>
</html>

Some useful commands:

podman help
podman run --help

(similar for other podman commands).

podman inspect -l

gives lots of information about the latest container (-l). Replace -l with the name of the container, as reported by podman ps, if needed.

podman logs -l

shows the latest container’s logs.

podman top -l

shows the output of top for the latest container.

podman stop -l

stops the latest container. Run podman ps -a to confirm it is stopped.

podman rm -l

to remove the latest container (confirm with podman ps -a, which should no longer list that container).

List all images on machine:

podman images

Remove an image:

podman image rm <image id>

where <image id> is as shown by podman images.

Simple example 2 (interactive arch linux container)

Find image:

podman search docker.io/alpine --filter=is-official

Run an image of alpine linux:

podman run --rmi -it docker.io/library/alpine

-it means a (pseudo) terminal will be started on the container and our stdin will be connected to that terminal’s stdin. --rmi means the container and image will be removed when exiting the container (--rm will just remove the container). See https://hub.docker.com/_/alpine for more details on this image.

Check can ping remote hosts:

ping 8.8.8.8

Check dns resolving works:

ping dns.google.

LAMP server

A LAMP server is a web server running linux/apache/mysql (or mariadb)/php.

Will create a “pod” (ie a group of containers). One container will have an apache server with php. The other will run mariadb. Specific (old) version of php and mariadb are chosen. Mariadb (not mysql) is chosen as official arm64 images do not exist for mysql for the versions I want.

See https://hub.docker.com/_/php and https://hub.docker.com/_/mariadb for more details on the php and mariadb images.

mkdir -p ~/podman/php7.4.25-apache-mariadb10.4
cd ~/podman/php7.4.25-apache-mariadb10.4

# Pull specific version of php/apache
id=$(buildah from docker.io/library/php:7.4.25-apache)
# Choose the php environment (development or production), eg
buildah run $id /bin/sh -c 'cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini'

# Enable the php mysqli extension
buildah run $id docker-php-ext-install mysqli

# Commit image.
# The committed image will be called php_7.4.25-apache-dev - "buildah images" will list it with a "REPOSITORY" value of localhost/php_7.4.25-apache-dev
buildah commit $id php_7.4.25-apache-dev
mkdir html

# Start a container (named 'php7.4.25-apache-test') from this image. Make it part of a new pod with the name 'php7.4.25-apache-mariadb10.4-test'.
# Make /var/www/html on the container available as ./html on the host. Make apache available on port 8000 on the host, and mariadb available
# on port 33000 (both on localhost interface only). Remove the "127.0.0.1:" to make the ports available on all interfaces.
podman run -d --pod new:php7.4.25-apache-mariadb10.4-test --name php7.4.25-apache-test -p 127.0.0.1:8000:80 -p 127.0.0.1:33000:3306 --volume ./html:/var/www/html php_7.4.25-apache-dev

# Pull latest 10.4 version of mariadb and commit as new image.

id=$(buildah from docker.io/library/mariadb:10.4)
buildah commit $id mariadb_10.4

mkdir mysql

# Start a mariadb container (named 'mariadb10.4-test') and add it to the pod
podman run -d --pod php7.4.25-apache-mariadb10.4-test --name mariadb10.4-test --env MYSQL_ROOT_PASSWORD=mariadbpwd --env MYSQL_USER=testuser --env MYSQL_PASSWORD=testpwd --env MYSQL_DATABASE=testdb --volume ./mysql:/var/lib/mysql mariadb_10.4

The --env settings set up the db root password, a second user testuser, and a db testdb. testuser only has access to this db. This is only done if the mysql/ directory is empty (ie the databases have not yet been created).

(Useful pod-related commands: podman pod ps (list pods), podman pod stop <podname>, podman pod start <podname>, podman pod rm <podname> (removes all containers in the pod as well as the pod).)

Test LAMP server

Create html/index.php with contents:

<?php phpinfo() ?>

and point a browser (eg links if eg logged in remotely to rpi5) at http://localhost:8000/ (should show a dump of the php setup).

Access the db (on Arch Linux, install package mariadb-clients on the host):

mariadb --user=testuser --password=testpwd --port=33000 --skip-ssl

Run eg

show databases;
quit

to list the dbs this user has access to (should be information_schema and testdb in this example), then quit.

Can log in to either container with

podman exec -it php7.4.25-apache-test /bin/bash

or

podman exec -it mariadb10.4-test /bin/bash

(the container names following -it matching the names given with --name above).

Try a simple php script accessing the database

Create html/show_dbs.php with the contents:

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // will throw a mysqli_sql_exception on error

$mysqli = new mysqli('127.0.0.1', 'testuser', 'testpwd'); // not 'localhost' (which will try to use a socket to connect) - '127.0.0.1' forces a network connection

$result = $mysqli->query('SHOW DATABASES');

$rows = $result->fetch_all(MYSQLI_ASSOC);

print_r($rows);

$mysqli->close();
?>

Browse to http://localhost:8000/show_dbs.php. The page should contain a list of the databases in the mysql container. In the above setup, it would list:

Array ( [0] => Array ( [Database] => information_schema ) [1] => Array ( [Database] => testdb ) )

Notes

Refs

[1]
“What is podman?” [Online]. Available: https://docs.podman.io/en/latest/