Chapter 7. Running containers as systemd services with Podman

Podman (Pod Manager) is a fully featured container engine that is a simple daemonless tool. Podman provides a Docker-CLI comparable command line that eases the transition from other container engines and allows the management of pods, containers and images. It was not originally designed to bring up an entire Linux system or manage services for such things as start-up order, dependency checking, and failed service recovery. That is the job of a full-blown initialization system like systemd.

Red Hat has become a leader in integrating containers with systemd, so that OCI and Docker-formatted containers built by Podman can be managed in the same way that other services and features are managed in a Linux system. This chapter describes how you can use the systemd initialization service to work with containers in two different ways:

  • Starting Containers with systemd: By setting up a systemd unit file on your host computer, you can have the host automatically start, stop, check the status, and otherwise manage a container as a systemd service.
  • Starting services within a container using systemd: Many Linux services (Web servers, file servers, database servers, and so on) are already packaged for Red Hat Enterprise Linux to run as systemd services. If you are using the latest RHEL container image, you can set the RHEL container image to start the systemd service, then automatically start selected services within the container when the container starts up.

The following two sections describe how to use systemd container in those ways.

7.1. Starting containers with systemd

When you set up a container to start as a systemd service, you can define the order in which the containerized service runs, check for dependencies (like making sure another service is running, a file is available or a resource is mounted), and even have a container start by using the runc command.

This section provides an example of a container that is configured to run directly on a RHEL system as a systemd service. To learn about automatic generation of systemd service file, see Generate systemd unit file.

  1. Get the image you want to run on your system. For example, to use a minimal image based on Alpine Linux from docker.io, run the following command:

    # podman pull docker.io/library/alpine:latest
  2. Configure the container as a systemd service by creating the generic unit configuration file in the ~/.config/systemd/user directory. For example, the contents of the ~/.config/systemd/user/container.service can look as follows:

    # cat ~/.config/systemd/user/container.service
    [Unit]
    Description=Podman in Systemd
    
    [Service]
    Restart=on-failure
    ExecStartPre=/usr/bin/rm -f /%t/%n-pid /%t/%n-cid
    ExecStart=/usr/bin/podman run --conmon-pidfile  /%t/%n-pid  --cidfile /%t/%n-cid -d alpine:latest top
    ExecStop=/usr/bin/sh -c "/usr/bin/podman rm -f `cat /%t/%n-cid`"
    KillMode=none
    Type=forking
    PIDFile=/%t/%n-pid
    
    [Install]
    WantedBy=multi-user.target
    • The Restart=on-failure line sets the restart policy and instructs systemd to restart the service when it cannot be started or stopped cleanly, or when the process exits with a non-zero status.
    • The ExecStart line describes how we start the container.
    • The ExecStop line describes how we stop and remove the container.

      You can run an alpine:latest container in the background that runs top. The podman run command includes two command-line options:

    • The --conmon-pidfile option points to a path to store the process ID for the conmon process running on the host. The conmon process terminates with the same exit status as the container, which allows systemd to report the correct service status and restart the container if needed.
    • The --cidfile option points to the path that stores the container ID.
    • The %t is the path to the run time directory root, for example /run/user/$UserID.
    • The %n is the full name of the service.

    For example, if the service name is container and the user ID is 1000, the above configuration places the conmon-pidfile in /run/user/1000/container.service-pid and the cidfile in /run/user/1000/container.service-cid.

  3. To reload systemd manager configuration, type:

    # systemctl --user daemon-reload
  4. To enable the service and start it at boot time, type:

    # systemctl --user enable container.service
  5. To start the service immediately and check the status of the service, type the following:

    # systemctl --user start container.service
    # systemctl --user status container.service
    * container.service - Podman in Systemd
       Loaded: loaded (/home/valentin/.config/systemd/user/container.service; disabled; vendor preset: enabled)
       Active: active (running) since Mon 2019-11-18 15:32:56 CET; 1min 5s ago
      Process: 189705 ExecStartPre=/usr/bin/rm -f //run/user/1000/container.service-pid //run/user/1000/container.service-cid (code=exited, status=0/SUCCESS)
      Process: 189706 ExecStart=/usr/bin/podman run --conmon-pidfile //run/user/1000/container.service-pid --cidfile //run/user/1000/container.service-cid -d alpine:latest top (code=exited, status=0/SUCCESS)
     Main PID: 189731 (conmon)
       CGroup: /user.slice/user-1000.slice/user@1000.service/container.service
           	├─189724 /usr/bin/fuse-overlayfs [...]
           	├─189726 /usr/bin/slirp4netns [...]
           	├─189731 /usr/bin/conmon [...]
           	└─189737 top
  6. To list containers that are running or have exited, type:

    # podman ps
    CONTAINER ID  IMAGE                        	COMMAND  CREATED     	STATUS         	PORTS  NAMES
    f20988d59920  docker.io/library/alpine:latest  top  	12 seconds ago  Up 11 seconds ago     	funny_zhukovsky
  7. To stop container.service, type:

    # systemctl --user stop container.service

To learn more about configuring services with systemd, refer to the System Administrator’s Guide chapter called Managing Services with systemd and article Running containers with Podman and shareable systemd services.

7.2. Starting services within a container using systemd

A package with the systemd initialization system is included in the official Red Hat Enterprise Linux Init base image named registry.access.redhat.com/ubi8/ubi-init This means that applications created to be managed with systemd can be started and managed inside a container. A container running systemd will:

  • Start the /sbin/init process (the systemd service) to run as PID 1 within the container.
  • Start all systemd services that are installed and enabled within the container, in order of dependencies.
  • Allow systemd to restart services or kill zombie processes for services started within the container.

The general steps for building a container that is ready to be used as a systemd services is:

  • Install the package containing the systemd-enabled service inside the container. This can include dozens of services that come with RHEL, such as Apache Web Server (httpd), FTP server (vsftpd), Proxy server (squid), and many others. For this example, we simply install an Apache (httpd) Web server.
  • The httpd and vsftpd packages are included in the UBI repositories. You would need a RHEL subscription to install the squid package.
  • Use the systemctl command to enable the service inside the container.
  • Add data for the service to use in the container (in this example, we add a Web server test page). For a real deployment, you would probably connect to outside storage.
  • Expose any ports needed to access the service.

In this example, we build a container by creating a Dockerfile that installs and configures a Web server (httpd) to start automatically by the systemd service (/sbin/init) when the container is run on a host system.

  1. Create Dockerfile: In a separate directory, create a file named Dockerfile with the following contents:

    FROM registry.access.redhat.com/ubi8/ubi-init
    RUN yum -y install httpd; yum clean all; systemctl enable httpd;
    RUN echo "Successful Web Server Test" > /var/www/html/index.html
    RUN mkdir /etc/systemd/system/httpd.service.d/; echo -e '[Service]\nRestart=always' > /etc/systemd/system/httpd.service.d/httpd.conf
    EXPOSE 80

    The Dockerfile installs the httpd package, enables the httpd service to start at boot time (i.e. when the container starts), creates a test file (index.html), exposes the Web server to the host (port 80), and starts the systemd init service (/sbin/init) when the container starts.

  2. Build the container: From the directory containing the Dockerfile, type the following:

    # podman build --format=docker -t mysysd .
  3. Open Selinux permission. If SELinux is enabled on your system, you must turn on the container_manage_cgroup boolean to run containers with systemd as shown here (see the Containers running systemd solution for details):

    # setsebool -P container_manage_cgroup 1
  4. Run the container: Once the container is built and named mysysd, type the following to run the container:

    # podman run -d --name=mysysd_run -p 80:80 mysysd

    From this command, the mysysd image runs as the mysysd_run container as a daemon process, with port 80 from the container exposed to port 80 on the host system.

  5. Check that the container is running: To make sure that the container is running and that the service is working, type the following commands:

    # podman ps | grep mysysd_run
    a282b0c2ad3d  localhost/mysysd:latest  /sbin/init  15 seconds ago  Up 14 seconds ago  0.0.0.0:80->80/tcp  mysysd_run
    # curl localhost/index.html
    Successful Web Server Test

At this point, you have a container that starts up a Web server as a systemd service inside the container. Install and run any services you like in this same way by modifying the Dockerfile and configuring data and opening ports as appropriate.