How to Install and Use Podman on Ubuntu 20.04

Last Updated: Wed, Sep 16, 2020
Containers Ubuntu

Introduction

Podman (the POD MANager) is a daemonless tool for managing Open Container Initiative (OCI), Docker containers schema 1, Docker containers schema 2, pods (groups of containers), images and volumes. While the podman CLI client aims to be compatible with the docker commands and sub-commands, Podman differs from Docker in two respects that are worth calling attention to:

  1. Podman containers run unprivileged (rootless) by default.
  2. There is no daemon (service) running.

In this tutorial, we will install Podman on Ubuntu 18.04 and use it to start containers and manage containers as a root and non-root user. Converting containers and their workflows to be rootless with the minimum capabilities required to run is journey of learning to crawl, walk then run. This tutorial will give you a good start to that journey.

Prerequisites

This guide applies to:

  • Ubuntu 20.04 LTS
  • Ubuntu 18.04 LTS

Podman Overview

The Podman project scope statement provides good summary of the functionality of Podman:

  • Support for multiple container image formats, including OCI and Docker images.
  • Full management of those images, including pulling from various sources (including trust and verification), creating (built via Containerfile or Dockerfile or committed from a container), and pushing to registries and other storage backends.
  • Full management of container lifecycle, including creation (both from an image and from an exploded root filesystem), running, checkpointing and restoring (via CRIU), and removal.
  • Support for pods, groups of containers that share resources and are managed together.
  • Resource isolation of containers and pods.
  • Support for a Docker-compatible CLI interface.
  • Support for a REST API providing both a Docker-compatible interface and an improved interface exposing advanced Podman functionality.
  • In the future, integration with CRI-O to share containers and backend code.

1. Install Podman

Ensure some system identifying data is available (we will use VERSION_ID).

$ source /etc/os-release

Add the Podman debian package repository to Apt.

$ sudo sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
$ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key -O- | sudo apt-key add -
$ sudo apt-get update -qq
$ sudo apt-get -qq --yes install podman

2. Executing Podman

Confirm Podman has been installed correctly.

$ podman info

You should see the Podman configuration and version information of the various components.

Using Podman from a terminal/shell consists of passing a chain of options, commands then arguments. The syntax takes this form:

podman [option] [command] [arguments]

The v1 and v2 commands are in most cases the same as the docker CLI. Podman strives for Docker compatibility, so you can generally substitute podman for docker in documents and blog posts code examples.

To review Podman options and commands:

$ podman --help

To view any sub-commands and options for a command:

$ podman <command> --help

3. Working with OCI Registries

Podman supports multiple container registries. When you specify a container name that does not contain a registry, e.g. store/elastic/metricbeat:7.9.0 rather than docker.io/store/elastic/metricbeat:7.9.0, Podman will consult the registry configuration file (/etc/containers/registries.conf) to obtain a list of registries to pull the container image from.

Add docker.io and registry.access.redhat.com (you can add some other registries too).

Edit /etc/containers/registries.conf:

$ sudo nano /etc/containers/registries.conf

Paste the follow contents:

# This is a system-wide configuration file used to
# keep track of registries for various container backends.
# It adheres to TOML format and does not support recursive
# lists of registries.

# The default location for this configuration file is
# /etc/containers/registries.conf.

# The only valid categories are: 'registries.search', 'registries.insecure', 
# and 'registries.block'.

[registries.search]
registries = ['docker.io', 'quay.io', 'registry.access.redhat.com']

# If you need to access insecure registries, add the registry's fully-qualified name.
# An insecure registry is one that does not have a valid SSL certificate or only does HTTP.
[registries.insecure]
registries = []

# If you need to block pull access from a registry, uncomment the section below
# and add the registries fully-qualified name.
#
# Docker only
[registries.block]
registries = []

Save and exit the file.

4. Using Podman with Sudo

While podman runs rootless by default you will sometimes come across a container that requires root privileges to build, or run the container. Generally, this is because Docker is run with root privileges by default, and many Dockerfiles and containers have been developed, built and run in a root environment. Since Docker Engine v19.03 it is possible to configure Docker to run rootless. However, configuring Docker for rootless operation is not trivial, so you will find most existing Docker tutorials and blog posts develop, build and run Docker containers as root.

Using sudo podman ... is generally sufficient to make the Podman build-time and run-time emulate the Docker behavior.

To better understand the context and implications of running Podman as root compared to non-root run sudo podman info and compare the output to that obtained previously from running podman info. Specifically, note the different settings for host:remoteSocket:path:, store:configFile:, store:graphRoot:, store:runRoot: and store:volumePath:.

Next, download and run the hello-word image with the following command:

$ sudo podman run hello-world

You should see the following output:

Trying to pull docker.io/library/hello-world...Getting image source signatures
Copying blob 1b930d010525 done
Copying config fce289e99e done
Writing manifest to image destination
Storing signatures
Hello from Docker!

This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:

    The Docker client contacted the Docker daemon.
    The Docker daemon pulled the hello-world image from the Docker Hub.
    The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading.
    The Docker daemon streamed that output to the Docker client, which sent it to your terminal.

The container cannot tell that it was run by Podman rather than Docker - generally this does not matter. Consequently, you can substitute Podman client for Docker daemon in the above output and have a reasonable summary of the actions taken - in fact Podman uses the Skopeo (container transfer) and Buildah (container build) libraries to implement some functionality. However, the essential difference is that no daemon (service) is in use.

5. Using Podman without Sudo

Display the current (non-root) user ID number (uid) on the host system.

$ id -u $(whoami)
1000

Check Podman rootless configuration is set up properly users.

$ podman unshare cat /proc/self/uid_map
     0       1000          1
     1     100000      65536

Then for groups.

$ podman unshare cat /proc/self/gid_map
     0       1000          1
     1     100000      65536

This shows how uids and gids are mapped from the user namespace inside the container to the host uids. Specifically, the uid of 0 inside the container is mapped to the current host system user ID, which is 1000. While uid value 1 inside the container is mapped to uid value 100000 on the host system. So a container uid value 47 would map to a uid value 100046 on the host system.

📝 Note: This means rootless Podman runs as root within the container, and that root process inside the container runs as the host system non-root user’s UID. Whereas rootful Podman runs as root within the container, and a root process inside of the container runs as the host system user with root permissions.

Consider two scenarios when a container root process escapes the container:

  1. Podman rootless (podman run ...): The process would have full access to the capabilities and permissions of the host system non-root user that executed podman run ....
  2. Podman rootful (sudo podman run ...): The process would have full access to the capabilities and permissions of the host system root user that executed sudo podman run ....

To be able to use the host system elevated uid values we need to be in the same user namespace that Podman employs when it runs a container with rootless permissions. The podman unshare ... command does just this.

A common use case will help make this clearer. Often a web or database server runs inside a container and is provided some files on the host system via a ---volume /host/path:/container/path option. For example:

$ podman run --rm --detach --tty --publish 8080:80 --volume ${PWD}/html:/usr/share/nginx/html nginx

Just as often the file permissions on the host may prevent the container user reading from, or writing to, the shared files. In this case the container uid for the nginx user is:

$ podman run --rm nginx bash -c 'echo "$(id -u nginx)"'
101

If permissions on the host system need to be changed, we use the the mapping uid discussed above. This can only be done from the user namespace that Podman sets up when it runs the container. Change the host system permissions so the container user nginx owns the shared files.

$ podman unshare chown 101:101 ${PWD}/html

A sensible container practice is to create a non-root user inside the container, e.g. nginx, and run processes inside the container as that non-root user. If the process running as nginx escapes the container it will have the capabilities and permissions of the host system uid the non-root container user maps to, e.g. for nginx this is 100100. Generally this uid will not exist outside the namespace the process escaped from.

Check host system directories, then files that are accessible by host system uid value 100100.

$ find ${PWD} -type d -uid 100100
$ find ${PWD} -type f -uid 100100

To try something more practical, you can access the shell within an Ubuntu container.

$ podman run --rm --interactive --tty ubuntu bash

You now have a bash shell running in the Ubuntu container. Type exit and press RETURN to shutdown the container and return to the host system.

6. Working with Podman Images

Search the registries you have configured Podman to use.

$ podman search ubuntu-18.04

Show the images that have been downloaded by podman.

$ podman images

Next, you can download an image as rootless user.

$ podman run hello-world

📝 Note that previously we downloaded the hello-world image as root user, which meant it is stored (by default) a different location, unaccessible to the non-root users. To avoid downloading the image again:

$ sudo podman save hello-world | podman load

To see the list of downloaded images:

$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/hello-world latest fce289e99eb9 6 months ago 6.14 kB

To see the running containers:

$ podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c6c20824a698 docker.io/library/hello-world:latest /hello 3 minutes ago Exited (0) 3 minutes ago upbeat_elion

You can stop then start the most recently used container:

$ podman stop --latest
$ podman start --latest

To remove the container:

$ podman rm --latest

7. Committing Changes in a Container to an Image

Any changes made to a container image are lost once the container is stopped. To commit changes you have made to a container, before stopping or exiting the container:

$ podman commit \
       --message "Statement of changes" \
       --author "Author Name <example@example.com>" \
       <cntnr_id> repository/new_name

8. Pushing Images to a OCI Repository

Register as a user with a registry, e.g. hub.docker.com, then login to the registry.

$ podman login docker.io

To push the local container that you built or committed changes to:

$ podman push <image_id> <docker-registry-username>/<docker-image-name>

Where <image_id> is the value in the IMAGE ID column printed by podman images, for the container image you wish to upload to the registry. The docker-registry-username is the account or organization name provided by the container registry, and docker-image-name is the name you wish to save the container as.

9. Running Containers as Services

Because podman containers run as the host system user by default you can easily run containers as user services. In the following we will create a systemd managed user service to start, and keep running, a rqlite replicated relational database. The --file option writes the systemd service description to a file instead of the terminal stdout. The --name option creates Podman commands that use container/pod names rather then ID values. The --new option creates a systemd unit file that will pull and remove containers at service start and stop - see ExecStartPre and ExecStopPost service actions. This allows the service to launch on a system where the container has never been used before the service is run.

$ podman create --name rqlite-5.4.0 rqlite/rqlite:5.4.0
$ podman generate systemd \
         --files \
         --name \
         --new \
         --restart-policy=always \
         rqlite-5.4.0

Review the generated unit file, and change the line ExecStart by inserting the text --publish 4001:4001 --publish 4002:4002.

ExecStart=/usr/bin/podman run \
          --conmon-pidfile %t/container-rqlite-5.4.0.pid \
          --cidfile %t/container-rqlite-5.4.0.ctr-id \
          --cgroups=no-conmon \
          --detach \
          --replace \
          --publish 4001:4001 \
          --publish 4002:4002 \
          --name rqlite-5.4.0 \
          rqlite/rqlite:5.4.0

Now we can place the unit file in the host system user's folder, enable, start and check the service status.

$ mkdir -p $HOME/.config/systemd/user/
$ cp container-rqlite-5.4.0.service $HOME/.config/systemd/user/container-rqlite-5.4.0.service
$ systemctl --user enable container-rqlite-5.4.0
$ systemctl --user start container-rqlite-5.4.0
$ systemctl --user status container-rqlite-5.4.0

In the output produced by systemctl --user status container-rqlite-5.4.0 you should see a line Active: active (running). We can also check the Podman report of the container details.

$ podman inspect rqlite-5.4.0 | grep Status
"Status": "running",
            "Status": ""

The second Status line reports the healthcheck status which we did not configure. To add health checks, edit the ExecStart in the **container-rqlite-5.4.0.service* file to:

ExecStart=/usr/bin/podman run \
          --conmon-pidfile %t/container-rqlite-5.4.0.pid \
          --cidfile %t/container-rqlite-5.4.0.ctr-id \
          --cgroups=no-conmon \
          -d \
          --replace \
          --publish 4001:4001 \
          --publish 4002:4002 \
          --healthcheck-command 'CMD-SHELL curl http://localhost:4001 && curl http://localhost:4002 && echo "Okay" && exit 0 || exit 1' \
          --healthcheck-start-period 5s \
          --healthcheck-retries 5 \
          --name rqlite-5.4.0 \
          rqlite/rqlite:5.4.0 -node-id 1

Update the service file, reload and restart it, then inspect the Podman status data.

$ cp container-rqlite-5.4.0.service $HOME/.config/systemd/user/container-rqlite-5.4.0.service
$ systemctl --user daemon-reload
$ systemctl --user restart container-rqlite-5.4.0
$ podman inspect rqlite-5.4.0 | jq '.[0]["State"]["Healthcheck"]'

10. Podman Privileges and Capabilities

Capabilities are Linux Kernel features for restricting security access inside containers. You may come across docker, or podman, commands that use the --privileged option.

$ sudo podman run --privileged ...
$ podman run --privileged ...

This is the same as granting the container user all capabilities inside the container.

$ sudo podman run --cap-add=all
$ podman run --cap-add=all

📝 Note: Running a container as root does not mean the container has all capabilities added. Sometimes you may need to run a container as root, as well as add additional capabilities. Try to avoid running containers with --privileged.

To inspect the capabilities a container is using:

$ podman inspect rqlite-5.4.0 | jq '.[0]["EffectiveCaps"]'

To remove the all capabilities a container has access to:

$ podman run --interactive --tty --cap-drop=all --name ubuntu-bash ubuntu bash

To check the capabilities have been dropped, do not exit from the ubuntu-bash container, and in another terminal, run:

$ podman inspect ubuntu-bash | jq '.[0]["EffectiveCaps"]'
null

📝 Note: Without prior experience and knowledge it is difficult to establish the minimum set of capabilities a container requires without engaging in iterative trials. How many iterations are required will change as you progress on your rootless container journey.

Conclusion

Congratulations! You have successfully installed Podman on the Ubuntu 18.04 server. You can now easily create, run, maintain and secure containers using Podman. Installing Podman has also provided you the ability to run containers as non-root without any additional configuration effort. Furthermore, you can easily emulate the Docker behavior by running sudo podman .... However, using containers as a non-root user can require some iteration if the container was developed, built and used under the assumption of rootful access. The Podman repository has a summary and status of rootless shortcomings. There is also a handy Troubleshooting guide.

For more information, please review the Podman documentation.

References

Want to contribute?

You could earn up to $300 by adding new articles