Ultimate Docker to Podman Migration Guide: It’s NOT difficult

Leaving Docker is like taking away your favorite old teddy bear – painful at first, but you’ll grow from it! Let’s migrate from Docker to Podman. This guide will help make your transition easier.

In my previous article, I wrote on the benefits of using Podman as a replacement for Docker.

This guide is intended to be a follow up to that article by giving the reader a working example of how one might migrate from using Docker to Podman.

I will use a few examples taken from Anand's fantastic Docker Media Server article. [Read: 60+ Best Docker Containers for Home Server Beginners 2023]

Editor's Note: This post was written by Kristopher based on his experience. Anand's Docker-Traefik GitHub repo will continue to use Docker and there are no plans to switch over to Podman. Also, just because Podman offers some advantages does not mean that Docker is unfit for the purpose most homelabers use it for.

About This Podman Migration Guide

Recall, Podman is simply a container runtime. What this means for you is that all of the introductory content written by Anand on containerization applies here as well. Therefore, I will jump straight into the details of getting your Docker containers running in a Podman environment. There are some new concepts to learn along the way; please take your time, take notes, and read through each section.

I have done my best to write this guide at a novice level. But some of what I am going to cover I consider to be intermediate-level topics. If this is your first time diving into containers or the Linux command line, I recommend starting with Anand's Docker guides.

That being said, I do not intend for this to be a "just copy and paste what you see here" guide. Instead, I hope to give the reader some tools to build their own server in a way they see fit. After all, it is YOU who is responsible for what you decide to run. Knowing HOW it was put together will only aid you in the long run.

Are you considering switching from Docker to Podman?

View Results

Loading ... Loading ...

Prerequisites for Replacing Docker with Podman

Podman is the native container runtime in all Red Hat based systems. This means it's very well integrated as the default for users of RHEL, CentOS, Fedora, AlmaLinux, Rocky Linux, and other derivatives. Personally, I prefer this ecosystem as it comes with other features I find useful.

However, I know many readers of this site tend to use Ubuntu and other Debian based distributions. Thus, this guide will be written using Ubuntu 22.04 LTS Jammy Jellyfish as a base. As such I must warn you: Podman support on Ubuntu isn't great; the repositories are stuck many versions behind. That means there is some functionality that does not work as expected, and it might take a bit of finessing. Consider yourself warned.

To follow along, you will need:

  • Ubuntu server 22.04 LTS
  • Basic understanding of CLI Linux
  • Basic understanding of containerization
  • Basic understanding of YAML file format
  • And most importantly: Patience

Coming from Docker to Podman

This article is written from a standalone perspective, meaning a bare server with nothing installed is preferred. However, since many of you will be transitioning from Docker on the same installation, I will give some tips along the way to help ease the transition. The tips will be in blue boxes like the following statement below. For those of you following along from a standalone perspective, you can ignore these boxes.

Docker Users:
I will begin by shutting down Docker (but not uninstalling it immediately).

sudo docker-compose -f docker-compose.yml down

Verify you don't have any stray containers still running:

sudo docker ps -a

Stop Docker Before Continuing
Stop All Of Your Running Containers To Avoid Conflicts.

You should see no other containers running. If you have others listed, be sure to stop and remove them as well. Leaving containers running could interfere with Podman if they are bound to ports we want to use via Podman.

Intro to the World of Rootless Containers

I know, the last thing you want is some theory to get started. But I promise to keep it short, concise, and most importantly, relevant to the guide: Namespaces vs Rootless Namespaces.

In my previous article, I discussed one of the biggest benefits of using Podman is its rootless design. I gave some examples, but now it's time to put the theory into practice. The below sections might be a bit dense, but do your best to follow along. If it's too much, move forward to the next section and return here when I refer to it later in the article.

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here

Install Podman

Podman.io Website
The Official Podman Website.

Podman can easily be installed with the standard Ubuntu repositories. As of writing this, Podman in the official Ubuntu repositories is at version 3.4.4.

In February 2022, a large update to Podman (version 4.0+), was made which rewrote their entire networking stack. The only limitation I have found with Podman version 3.4.4 is that networking between containers can be difficult at times, even within the same pod. Regardless, everything still works and this article will stick with Podman version 3.4.4.

sudo apt install podman

Installing Podman With Apt
Installing Podman Brings Lots Of Other Tools Like Buildah For Building Images.

By installing Podman, we will be getting a load of other supporting software along with the program itself. As stated in my previous article, Podman is actually a set of different tools that perform different functions in the container ecosystem. I'll spare you the details of what each software does, but if it interests you, there are numerous articles on each software.

Podman-compose

Just like Docker, Podman is able to work with compose files - greatly speeding up deploying, redeploying containers, and even transition from Docker to Podman. Podman-compose is a program written in the Python programming language. Thus, the easiest way to install it is via Python's module installer called pip3, short for Package Installer for Python.

At the time of writing this, the pip repositories have the latest version of podman-compose at version 1.0.3. That was released 10 months ago, and there have been numerous fixes put into the as-of-yet unreleased version 1.0.4. For now we will stick on 1.0.3, but if there are breaking changes in 1.0.4, I will update this article.

pip3 install --user podman-compose==1.0.3
Note:

  • The install command above is used with the --user switch to only install it for the current user.
  • After the name of the package (podman-compose), we can specify which version we require. In this case, we are asking for the exact version with == : 1.0.3.
  • If you are receiving the error: Command 'pip3' not found, pip3 can be installed by issuing the command:
sudo apt install python3-pip

Install Podman-Compose Via Pip3
Install Podman-Compose Via Pip.

Hopefully everything installed fine and we are good to go! Move on to the next section.

Podman and Podman-compose

Fortunately for us, Podman is described as a "drop-in replacement" for Docker. And it's true.

All of the commands you are familiar with work the same with Podman as they did for Docker. Refer to Anand's Docker Beginners Guide for reference. In most cases, simply substitute the word "docker" for "podman" and you're in business.

Podman Commands Similar To Docker
The Podman Commands Are Identical To Docker.

One huge difference, however, is that we are running in a rootless environment. That means that we don't need to add our user to a Podman group, or use sudo when running Podman commands.

This does however come with a few caveats which I will discuss below.

Basic Podman Commands

Podman commands generally follow the exact same syntax as what you're familiar with for Docker and docker-compose. For example:

To show all containers (running and stopped):

podman ps -a

To start your compose file:

podman-compose -f docker-compose.yml up -d

The rest follow along exactly the same as Anand's article.

Pods

Podman Pods Example
Example Of How Pods Work.

One main difference from Docker to Podman is the use of Pods. Pods are a type of container holding a collections of containers. The primary use for pods is when you want to setup a single application that uses multiple containers.

Let's say, for example, you decide to setup Nextcloud which can use 3 different containers for even a basic installation. By putting them in the same pod, you can start and stop them together, and even interconnect them using "localhost" as if they were already in the same container.

When running containers alone, Podman will not create a pod to put them in by default. The containers can run standalone just like Docker. For example, you could put all the CrowdSec related containers in a pod.

How-To Series: Crowd Security Intrusion Prevention System
  1. Crowdsec Docker Compose Guide Part 1: Powerful IPS with Firewall Bouncer
  2. CrowdSec Docker Part 2: Improved IPS with Cloudflare Bouncer
  3. CrowdSec Docker Part 3: Traefik Bouncer for Additional Security
  4. CrowdSec Multiserver Docker (Part 4): For Ultimate Protection

As this post is following Anand's, there are no clear use cases for pods. Thus, I will not be covering them in this guide.

Setup Host system for Rootless Containers

Before diving straight into replacing Docker with Podman, we must take a few steps to setup our rootless environment.

When you run containers using Docker, ultimately, the root user is behind the actions. The consequence is that there are a few system restrictions that do not apply to the root user but will apply to our user. Let's take a look at them before we start using Podman.

Docker as an Image Source Registry

With Docker, pulling images occurs automatically using their own docker.io repository.

Podman however, was originally designed to run in the Red Hat environment and thus use Red Hat derived image repositories. Podman on Ubuntu, for some odd reason, has no default registries included at all! An annoyance, but an easy fix.

Podman No Registries Found
Oddly Enough, Podman On Ubuntu Comes With No Image Repositories Active.

Podman allows us add any, and as many, repositories as we'd like, including docker.io. By default, Podman will read the global settings from the /etc/containers/registries.conf file. To override it and add the Docker repository permanently, we will create a local settings file.

Copy the Registries Config File

Copy the above mentioned file, and place it in your user's ~/.config directory:

mkdir --parents /home/rairdev/.config/containers
cp /etc/containers/registries.conf /home/rairdev/.config/containers/
Note:
The first command creates the containers directory. If we don't have the .config directory already, the --parents flag will create it as well.

Add Docker as a Registry

Using your preferred text editor, open the file we just copied.

I found the comments within the file to be rather obtuse. So I compared its contents to the same file on my Fedora machine and quickly found that we just need to add docker.io as an "unqualified-search-registry". Head to the bottom of the config file and add:

unqualified-search-registries = ["docker.io"]
Note:
If you would like to add other registries to pull images from in the future, all you need to do is add them to the array (list):

unqualified-search-registries = ["docker.io", "registry.fedoraproject.org"]

Save and exit. Done! Every time you run Podman as your user, Podman will see this file and use your defined registries to find container images.

Create Registries Config
Creating And Modifying The Default Registries Config Allows Us To Pull The Image Now.

Enable Containers to Run After Logout

By default, when using Docker containers, logging-out of your server does not stop the containers. Why? Because the Docker process is running as the root user. The root user is allowed to continue running processes in the background even when not logged-in. As we will be running in a rootless environment, we don't have the same privilege by default.



Since we will be running containers as a regular user, we need to enable their processes to "linger", or remain active, even if not logged in.

sudo loginctl enable-linger rairdev

This simple one-liner will allow my user (rairdev) to run containers, logout, and keep the containers running in the background.


Allow Containers Use of HTTP/HTTPS Ports

A default security measure is to deny non-root users binding ports below 1024. Ports below 1024 are known as "privileged ports". We can test this by trying to run a container on port 80:

podman run --rm -p 80:80 nginx:alpine

Unprivileged Ports Permission Denied
Trying To Bind To Ports Under 1024 Results In An Error.

This is problematic as we want to access our services through the default HTTP/HTTPS (80 and 443 respectively) ports.

Take note: The error message tells us exactly what we need to do to - add a line to the /etc/sysctl.conf file.

Create a New Configuration File

We can follow the directions above exactly, but there's a risk that the /etc/sysctl.conf file gets overwritten during a system update. To ensure this setting doesn't get removed, we can create a separate sub-configuration file under /etc/sysctl.d/ with the corrected setting:

Sysctl Config File Header
Opening The Sysctl Config File Gives Us A Valuable Piece Of Info At The Very Top Of The File.

Note:
This line at the top of the /etc/sysctl.conf file tells us that the system will read variables from files in the /etc/sysctl.d/ directory.

Let's create a file in that directory, named something we will recognize later:

sudo -e /etc/sysctl.d/podman-privileged-ports.conf
Note:

  • Using sudo with the -e switch is a quick way to tell the system we want to "edit" the file listed.
  • We are creating a new file called podman-privileged-ports.conf within the /etc/sysctl.d/ directory, where other config files of the same nature are stored by default.

We are greeted with a blank file. Let's add a comment at the top so when our future-selves find this file, we will know where it came from!

# Lowering privileged ports to 80 to allow us to run rootless Podman containers on lower ports
# From: www.smarthomebeginner.com
# default: 1024

Below my comments, we just need to copy and paste the exact line from the error message shown above:

net.ipv4.ip_unprivileged_port_start=80

Save the file and exit.

Make the Setting Permanent

Finally, to load the setting we just created:

sudo sysctl --load /etc/sysctl.d/podman-privileged-ports.conf

You should see the setting printed in your console.

For those interested, we can run the quick test again to ensure it worked:

podman run --rm -p 80:80 nginx:alpine

Podman On Port 80
Now We Can Run A Container On Port 80, And See It Running In A Browser.

OK, great! Our system is setup and ready to use.

Setup our Podman Environment

Following along with Anand's previous article, he sets you up with a file and directory structure. For the purpose of simplicity, I will follow his way of doing things and keep the naming scheme identical.

Directories and Files

According to his article, we don't need much to get started. Follow along there for descriptions of what we're doing if needed. Let's begin by creating the docker directory, and inside it the appdata directory.

Docker Users:
We can easily re-use the same files and directories from your Docker setup, so no need to change anything here.

Permissions From Docker
These Permissions Won't Exactly Work For A "Rootless" Environment!

However, as we will be running rootless, make sure to change ownership of the files to your current user:

sudo chown -R rairdev:rairdev ~/docker

This will change any files you had owned as root to your user. This is critical or Podman won't be able to access those files!

mkdir -p ~/docker/appdata
cd docker
Note:
We are creating both directories in a single command just like we did above. This time I used the abbreviated -p flag which is the same as using --parent.

Next, we need both the .env environmental variables file, as well as the docker-compose.yml file iteself.

touch .env docker-compose.yml

And lastly, let's set some stricter permissions for the .env file since it could contain some sensitive information.

chmod 600 .env
Note:
In Anand's article, he sets the ownership of the .env file to the root user. For our rootless Podman environment, you can probably deduce that nothing can be owned by root for our user to use it. Thus we will keep it owned by our user.

What we lost in minimal security benefit we can make up for with Podman secrets.

Default Directory Permissions

Differing from Anand's article, we don't need to set a default group owner for our files. Having them owned by our user is perfectly sufficient. If you would like a tiny bit of added security, we can restrict access to other users in one of two ways:

  1. Using umask: The umask command can be used to set a default permission for newly created files and directories. It is used in a deductive fashion. Therefore, if we wanted to create new files without other users permissions added, we would use umask 007 to remove all (7 = rwx) permissions. Remember, you must run the umask command before creating any files or directories. However, the big caveat is that this is reset each time you logout and login.
  2. Using Access Control Lists (ACL's): Similar to Anand's article, we can use ACL's to set default permissions for the entire filesystem within our docker directory. This is a bit overkill, but can be done with:
                     sudo setfacl -Rdm o::--- /home/rairdev/docker
                     

    Here we modify (-m) the default (-d) ACL's for the docker directory recursively (-R) to remove all permissions (---) from other users (o::).

I will let you decide which method you prefer. Generally speaking, it is more critical to ensure you have followed some basic good-security practices for securing the host system from outside users.

Environmenal Variables in the .env File

The environmental variables help us to set recurring configuration settings in multiple containers. For the time being, you can use the exact same as what's in Anand's guide.

Docker Users:
You can also safely leave the PUID and PGID variables at 1000, but you may notice that your apps won't be able to access your previous data. If that's the case, you would need to change ownership of the files.

That's done with the chown command. However, things get a little tricky when using Podman within a user namespace. If you followed my explanation above, you would see that anything owned by user 1000 within the container, is actually user 100999 outside of the container.

So we can just use the following right?

chown -R rairdev:100999 /home/rairdev/docker/appdata/heimdall

Chown Error For User 100999
If These Subuids Belong To Our User, Why Doesn't This Work?

Oops! Not permitted! That's because we don't have permission to change ownership outside of our user in this namespace. That is a critical point. But what if we were the root user?

To become the "root" user within our Podman namespace, we use a command known as podman unshare. This drops us into our user namespace, so the following command is run as the "root" user from rootless Podman's perspective. So instead, it should look like:

podman unshare chown -R root:1000 /home/rairdev/docker/appdata/heimdall

NB: I changed the group to 1000 in the second command. Remember now I'm running this command within the user namespace. Have a look at the file ownership now:

Permissions Correct Podman Unshare
Using Podman Unshare Gives Us The Expected Permissions.

You may have noticed that I have left the PUID and PGID variables the same as Anand's Docker article. While I tried to find an easier work around, the LSIO images are quite particular and have some attributes that cause them to not play nice with Podman.

Building our Podman Stack

As mentioned in the introduction, this article is not intended to be a full replacement for the Docker guide, but simply a starting point for Podman. With that in mind, I'll get you started with a few apps, and some tips to keep in mind when using Podman instead of Docker.

Docker Users:
I recommend creating a new file to play with, while leaving the old docker-compose.yml file untouched. Call the new file anything you like - podman.yml for example. When we run our podman-compose up command, simply substitute the docker-compose.yml filename with the correct name you set.

Note that you do not need the "version" declaration at the top of your compose file as it has no effect in Podman. Leave it, delete it, there's no difference here.

Networks

Since we are using podman-compose version 1.0.3, we can declare networks in our compose file, but not control the subnet they will use.

Note:
If you absolutely need to use a specific subnet, check out the optional section just below.

Open your compose file and add the following:

########################### NETWORKS ###########################
# You may only customize the network subnet (192.168.89.0/24) below if you are using
# Podman-Compose version 1.0.4 or higher.
 
networks:
  npm_proxy:
    name: npm_proxy
    # ipam:
    #   config:
    #     - subnet: 192.168.89.0/24

(Optional) Create your NPM Proxy Network Separately

If you would like to use a spefic subnet with your npm_proxy network (or any other networks for that matter), you will have to manually create the network before starting your podman-compose file. This issue has been fixed in the unreleased version 1.0.4 of Podman-Compose.

Creating a network with a specific subnet in Podman is simple.

podman network create --subnet 192.168.89.0/24 npm_proxy

To verify the network, we can inspect it:

podman network inspect npm_proxy

Network Creation With Podman
A Small Workaround For Custom Subnets Until Podman-Compose V. 1.0.4

Extension Fields

If we are re-using certain declarations again and again, we can simplify our compose file by simply calling these extension field "groups". Below your networks declaration from above, we can add the extension fields similar (but not identical) to Anand's Docker article.

########################### EXTENSION FIELDS
# Helps eliminate repetition of sections
# More Info on how to use this: https://github.com/htpcBeginner/docker-traefik/pull/228
 
# Common environment values
x-environment: &default-tz-puid-pgid
  TZ: $TZ
  PUID: $PUID
  PGID: $PGID
 
# Keys common to some of the core services that we always to automatically restart on failure
x-common-keys-core: &common-keys-core
  networks:
    - npm_proxy
  security_opt:
    - no-new-privileges
  restart: always
 
# Keys common to some of the dependent services/apps
x-common-keys-apps: &common-keys-apps
  networks:
    - npm_proxy
  security_opt:
    - no-new-privileges
  restart: unless-stopped
 
# Keys common to some of the services in media-services.txt
x-common-keys-media: &common-keys-media
  networks:
    - npm_proxy
  security_opt:
    - no-new-privileges
  restart: "no"

Great! With that out of the way we will move onto the services themselves.

Services Declaration

Before adding containers, we simply need to identify where our container declarations will start. Give yourself a couple of lines below the extensions heading and add the following:

#################### SERVICES ##################
services:

Note that there are no spaces before the services: declaration. Any containers we declare after this, will begin with 2 spaces to denote that they will exist within this services: declaration.

Frontends

While not my favorite name for these pieces of software, they are designed to sit "in front" of your other containers/applications. Thus, they act as a "doorway" of sorts to our other containers. Both of these pieces of software are Graphical User Interfaces (GUI) that are not entirely necessary, but can be helpful if you are still learning and the command line is intimidating.

Be the 1 in 200,000. Help us sustain what we do.
114 / 150 by Dec 31, 2024
Join Us (starting from just $1.67/month)

Nginx Proxy Manager

Nginx Proxy Manager
A Gui For Nginx As A Reverse Proxy.

In this section, we will be adding Nginx Proxy Manager as a reverse proxy. This piece of software adds a nice user interface to the popular NGINX webserver common throughout the internet. Acting as a reverse proxy, NPM allows us to access all of our containers via a domain name (like https://service.example.com) instead of having to remember IP addresses and port numbers.

The setup is fairly straight forward and similar to Anand's. Open your compose file, and add the following:

  # Nginx Proxy Manager - Reverse Proxy with LetsEncrypt
  npm:
    <<: *common-keys-core # See EXTENSION FIELDS at the top
    container_name: nginx-proxy-manager
    # Use the fully qualified name to pull including the docker.io portion
    image: 'docker.io/jc21/nginx-proxy-manager:latest'
    # We will let Podman assign a Dynamic IP
    networks:
      - npm_proxy
    # If you REALLY need a static IP (normally not necessary) and are using podman-compose version <= 1.0.3,
    # you will need to create the npm_proxy network manually with podman, using the --subnet flag. If you've done that you can use the following:
    #  networks:
    #    npm_proxy:
    #      ipv4_address: 192.168.89.254
    ports:
      - '80:80' # Public HTTP Port. Port Forwarding on Router is ON.
      - '443:443' # Public HTTPS Port. Port Forwarding on Router is ON.
      - '81:81' # Admin Web Port. Port Forwarding on Router is OFF. Internal Home Network Access only.
    volumes:
      - $DOCKERDIR/appdata/npm/config:/config 
      - $DOCKERDIR/appdata/npm/letsencrypt:/etc/letsencrypt
      - $DOCKERDIR/appdata/npm/data:/data
    environment:
      DB_SQLITE_FILE: "/config/database.sqlite"
      DISABLE_IPV6: 'true'

Note that the main difference between the above and Anand's compose file is that I'm not setting a static IP. My personal preference is to not set static IP's at all, and instead refer to containers by their names. This allows any IP changes to be insignificant to us, as Podman's internal DNS will always point us to the correct address regardless.

After adding the above, save and close.

Before starting our container, recall that Podman does not alter our firewall rules like Docker does (another security enhancement).

Thus, you will need to open ports 80, 443, and 81 in ufw:

sudo ufw allow 80,443,81/tcp comment Nginx-Proxy-Manager

Now we can start the container using:

podman-compose -f docker-compose.yml up -d

Podman-Compose Running Npm
Using The Podman-Compose Up Command Is Quite Verbose.

Now let's have a look at the app itself: Open a browser on your desktop and type in http://your-server-ip:81

Note:
If you don't know your server's IP, you can find it with:

ip -4 address

I highly recommend setting your server as a static DHCP lease in your router!

This should bring you to the login page for Nginx-Proxy-Manager.

Npm Login Via Ip And Port
Npm Can Be Reached Using The Ip And Port For Now.

At this point you are at the same point in Anand's article titled: "Setting Up Reverse Proxy for Nginx Proxy Manager's Web Interface". Follow along to finish setting up your Nginx Proxy Manager with Podman.

Cockpit

Cockpit Homepage
Cockpit Can Manage And Monitor More Than Just Containers.

I realize many of you are probably wondering where Portainer has gone for this section, but see my note below on why I decided to exclude it. Also, I wanted to introduce you to another option for managing your server. If you are bent on using Portainer, it is possible to make it work with Podman using the instructions below.

Cockpit is a web-based GUI for your server, and has a nice plugin for us to manage Podman containers. It is admittedly limited in some respects, but uses Podman's REST API instead of the socket. I find this approach more modern and secure.

Cockpit Installation

Cockpit is not intended to be run as a container, but instead directly on the host. It can manage more than just Podman containers, but those are our focus today. Install directly on the host with:

sudo apt install cockpit cockpit-podman

Once installed, we can start it and enable it at boot time with systemd:

sudo systemctl enable --now cockpit.socket

If you decide to access it only via host IP address on your local network, open port 9090 in your ufw firewall like we did above. Connect to Cockpit with https://your-server-ip:9090.

Note that I have HTTPS in the address. This is due to the fact that, by default, Cockpit creates a self-signed TLS certificate. So you may get a security warning in your browser.

Cockpit Accept Risk Self-Signed
Firefox Detected A Self-Signed Tls Certificate And Is Warning Us About It.

It's safe to ignore, you can continue to use Cockpit.

Cockpit Login Tls Warning
We Move Straight To The Login Screen. Note The Warning Sign On The Tls Lock.

Access Cockpit Through Domain Name

If you'd like to access it through a domain name, either externally and/or internally, we have to make one change to the system. As a protective measure, Cockpit doesn't normally allow anything (like reverse proxies) in front of it. But we can change that behavior by creating Cockpit configuration file on the system just like we have before.

The configuration file is located at /etc/cockpit/cockpit.conf. Chances are this file does not exist yet, so we will create it and add the following:

Hint: Remember the sudo -e command from before?

[WebService]
# The domain name you'll be using goes here
Origins = https://some.domain.com
# Settings to ensure Cockpit works behind a reverse-proxy
ProtocolHeader = X-Forwarded-Proto
ForwardedForHeader = X-Forwarded-For

Save and close.

Restart Cockpit to enable the changes:

sudo systemctl restart cockpit.service

Now we head back to Nginx Proxy Manager. Create a new Proxy Host. After entering your desired domain name, there are a few critical settings to pay attention to:

Add Cockpit To Npm
Accessing Cockpit Via A Domain Name Includes Some Intricate Details.

  1. Scheme: The scheme must be set to https. This is due to Cockpit using self-signed certificates by default. If you are having trouble or would just like to use http, you will need to add AllowUnencrypted = true to the config file above.
  2. Forward Hostname / IP: We use the IP of the host. If we use localhost, or 127.0.0.1, Nginx Proxy Manager will not be able to find Cockpit. This is due to the way Podman creates and handles containers.
  3. Websockets Support: We must check this box for Cockpit to be accessible.

Choose your SSL settings as Anand has described and save.

You should now be able to reach Cockpit at the domain name you just set.

Using Cockpit

Login with your system credentials. On the left you will see lots of different sections with information about your system. You will also see a section called "Podman containers".

Podman Containers In Cockpit
Cockpit Has Many Features And Plugins, One Of Which Is For Podman!

Here we can see images we've downloaded and both running and stopped containers. Clicking the arrow to the left of the container name allows us to see more details, logs, and instantly start a console in the container (if the container image supports it).

Podman Controls Via Cockpit
While You Can't Do Everything Via Cockpit, There Are Some Nice Features.

I will let you discover the rest of Cockpit's features. Check the documentation for more.

Other monitoring tools

Since I have left out Portainer, I wanted to mention some options you have if you're looking for more Podman dashboard information.

Uptime-Kuma

Uptime Kuma For Containers
Uptime Kuma Can Be Used For A Quick Reference To See Your Container Status.

A nice dashboard for monitoring container status.

Grafana

Grafana Podman Visualization
Grafana Is A Highly Customizable Visualization Tool.

An infinitely customizable dashboard application that can take data from nearly any source and create a dashboard from it. Since we are talking Podman, here's an example of container stats.

You will also need Promethus Podman Exporter to export stats from Podman to Prometheus database.

Backends

I decided to keep this section short and give just a single example for now. Perhaps later I will expand this section with other apps. Per usual, more can be found in the SmartHomeBeginner GitHub Repository.

In your compose file, you might want to create a separator for each category of service; add a divider below your Nginx Proxy Manager if you'd like.

######################## DASHBOARDS ###################

I called mine Dashboards since this the next app on the list falls into the category. But you can name it anything you like, or follow Anand's naming scheme in the sister article.

Heimdall

Example Heimdall Dashboard
Heimdall Is A Nice Visual Aid For Organizing Applications.

As you'll probably add a ton of apps to your container stack, you might want a way to organize links to them in a visual dashboard. I find this especially useful for users who have a hard time remembering all of the URL's for different apps.

Heimdall Podman Compose

In your compose file, add the following to add the Heimdall dashboard to your stack:

  heimdall:
    <<: *common-keys-core # See EXTENSION FIELDS at the top
    image: lscr.io/linuxserver/heimdall
    container_name: heimdall
    # We don't need to assign ports if we are accessing the apps through nginx proxy manager. If you want to access it via the server's IP, assign a port
    # ports:
      # - "83:80" # 80 to 82 already taken by other services
      # - "444:443" # 443 used by Nginx Proxy Manager. Disabled because we will put Heimdall behind proxy.
    volumes:
      - $DOCKERDIR/appdata/heimdall:/config
    environment:
      <<: *default-tz-puid-pgid

Save and close the file.

Start the container with the usual command:

podman-compose -f docker-compose.yml up -d
Add Heimdall to Nginx Proxy Manager

Feel free to add Heimdall to Nginx Proxy Manager. Since the Heimdall container resides in the same npm_proxy network as Nginx Proxy Manager, it can be found by using http://heimdall:80.

Warning:
If your server is open to the internet, anyone fishing around could find your Heimdall dashboard! By default, there is no login or password. While not a huge security issue, by not setting any type of authentication, a bad actor can see whatever else you have running. I recommend limiting access to your local network, putting authorization and authentication in front of it, or using a Virtual Private Network (VPN) like Wireguard.

At the bare minimum, set a password for your dashboard.

Restart Containers and Start on Boot

Container restarts can be handled by Podman in much the same way Docker does. However the containers cannot be set to start on boot or after a reboot through Podman directly.

Fortunately, as stated in my previous article, Systemd to the rescue! If you are unfamiliar with Systemd and how your server relates to it, I recommend reading up on it. There are numerous articles explaining how it works.

The syntax and inner workings of a Systemd service can be complex.

Confusing Systemd Fie
Systemd Is A Complex But Powerful System.

Fortunately, Podman comes with a tool to generate these files for us using the commands and variables we used to create the container previously. So let's generate some Systemd unit files to have the system manage the containers at boot and restart as needed.

Systemd Unit files

The first thing we must consider is that we are running in a rootless environment. This means that we don't want to create Systemd files that require sudo to run. Fortunately, we can create Systemd unit files that can be run in our user space! No sudo required!

Create our Systemd Userspace Directory

By using Systemd as a user, the default location for Systemd service files is within our user's home directory.

Normally this directory does not exist by default, so we need to create the directory where systemd will look for the configuration files.

mkdir -p ~/.config/systemd/user
cd $_
Note:
The command cd is used to change directory. The $_ after is used to reference the last directory we interacted with. In this case, $_ will represent /home/rairdev/.config/systemd/user.

Generate the Systemd Files

Now that we have created the directory, and are in the directory, let's have Podman generate the Systemd unit files for us:

podman generate systemd --new --name --files nginx-proxy-manager
Note:

  • podman generate systemd uses a default template to create basic Systemd files for us.
  • The --new flag creates unit files that do not expect the container to already exist. Instead it will 'run' a new container each time.
  • The --name flag means the unit file will refer to the container name, and not its ID. Otherwise you will have files called something like container-9c878a22406711922fdb480ed66af1056cf162a20d6d92ab248e83b94c9351b4.service.
  • The --files flag means that the output will create a file instead of just outputting the result to your console.
  • And finally we use the name of the container we want to create the Systemd unit file for.

We can see that Podman has generated the Systemd files. Since I referenced a container, the Systemd unit file will be prefixed with "container-".

Podman Generated Systemd Files
Podman Adds 'Container-' To The Front Of Our Generated Filenames.

Repeat this step for any further containers you wish to have start at boot.

Restart Policy

You may have noticed I did not mention anything about a restart policy! By default, when using the podman generate systemd command, Podman defaults to "on-failure". This is generally good for most circumstances. The other policy options are listed in the link above, although there's not a lot of info on exactly how each acts.

If you'd like to set a different policy than the default, you can pass it as a command line option when generating the Systemd unit files:

podman generate systemd --restart-policy=always --new --name --files nginx-proxy-manager
Note:
By setting the restart policy to "always", the container will always try to restart. If you stop the container with podman stop, the container will restart itself via Systemd. If it is failing to start due to a bad configuration, it will be stuck in a continuous restart loop!

Enable to Start on Boot

Now that we have created the Systemd unit files, we simply need to enable them like we would any other service on our system.

systemctl --user enable --now container-nginx-proxy-manager
Note:

  • The critical piece here is the use of the --user flag. This allows even non-root users to create Systemd units.
  • By using enable --now, we enable the container service at boot time, and start the Systemd unit now to use the restart policy you've set.

And that's it! You're set.

Checking Status, Updates, and Other Notes

To check the status of your container's Systemd unit, the command is similar to nearly any other Systemd service:

systemctl --user status container-nginx-proxy-manager

Systemd Running Podman Container
Systemd Is Taking Care Of Ensuring The Container Is Running Properly!

Besides the usual podman ps -a command, we can also see which Systemd unit files have loaded correctly or failed:

systemctl --user list-units container\*

Systemd List Units
Our Units Are Loaded, Active, And Running!

Note:
By using container\*, we are asking Systemd to show us any units that start with (match) the name "container". An asterisk is used to match "anything", but must be "escaped" by putting the backslash "\" in front of it.

Images With Version Tags

Systemd File With Image Version
If We Give An Image Version In Our Compose File Or Run Command, It Will Be Kept By Systemd.

The last thing I wanted to mention is that if you are using version tags when running images, that version will be included in the Systemd unit file as shown above. Thus, if you update an app version in your compose file, you will need to re-generate the Systemd unit file. By running the same generate command as above, the new unit files will overwrite the old ones.

Afterwards, you will need to tell Systemd to look for an updated service file:

systemctl --user daemon-reload

There are more options available via Systemd (for example: container startup order) that you can read about in the Podman documentation.

Things to Know

Linuxserver.io Images

Linuxserver.io (LSIO) has spent a huge amount of time and resources building some amazing containers that prioritize simplicity and security. While these are much appreciated and necessary features in the Docker space, they have some characteristics that can be difficult to overcome using Podman.

For example: In rootless Podman, the "root" user (UID and GID 0) are actually just our user on the host, not the host system root user. However, many of the LSIO images do not allow running them as the "root" user within the container. I tried multiple ways trying to setup Podman (v. 3.4.4) to play nice with LSIO images, but some just would not allow me to override the error caused by this security feature.

It should be noted that some images do allow you to set the PUID and PGID environment variables to 0 without any issues. I have not tested many apps, but some work while others do not. YMMV.

That being said, LSIO images can easily be used in Podman, but there is no official support for Podman. The main thing to be aware of is the files and directories you bind to the container will be owned by a user other than your own on the host. Depending on how you (or other containers) access these files, it may or may not be an issue for your setup.

Create a System Group (Optional)

If, for example, you want to share a directory across containers, and also access it as your user, we can create a system group and add our user to it.

Assuming your user has an ID of 1000, and we have our PUID and PGID environmental variables set to 1000, files created by these images will be owned by user 100999 (see above for a more in-depth explanation).

sudo addgroup --gid 100999 media
sudo usermod -aG media rairdev
Note:

  • The first command is adding a group to our system called "media" with the group ID of 100999.
  • The second command is to add the group named "media" to our user's (rairdev) list of groups.

And now when I look at the files used by LSIO containers, I can open and modify them as long as they have permissions set for the group.

Media Group For Containers
Adding A Group Simplifies Access.

Note:
If a file or directory is giving "Permission Denied" errors, you can still access and edit them using the podman unshare command discussed above.

Portainer for Podman

I know many of you rely heavily on Portainer to manage your containers and view their status. I did not include it as one of the apps above for two reasons.

1. We Must Create and Expose a Podman Socket

This isn't ultimately that big of a deal as the socket has good permissions and only allows our user access to it. I just have a hard time exposing such a powerful feature to a piece of software under heavy development. Maybe I just have trust issues.

2. Only Partial Podman Support

After nearly 2 years of people requesting Podman support, users finally got their wish - well sort of. Portainer does work with Podman, but many features are missing and I personally found the experience to be less than stellar. It does appear, however, that the Portainer team is slowly building support for Podman. Keep an eye on this GitHub issue which is tracking the current support for Podman.

How do I Use Portainer with Podman?

I don't care about your trust issues; how do I use Portainer with Podman anyways!

For those of you insisting on continuing to use Portainer, the instructions for using it with Podman can be found in this post on their GitHub. Make sure to pay attention to use the "Rootless" section.

How do I Use an Updated Version of Podman?

As of writing, Podman in Ubuntu repositories is at version 3.4.4. I find this odd as there have been 3 security releases since then and it should be at least at 3.4.7.

The official Podman documentation does a good job of explaining how to install updated versions of Podman. It involves adding some repositories from Kubic. Unfortuantely, the "testing" repo is very far behind at version 3.4.2. However, if you're willing to live on the bleeding edge, "unstable" has just released the most updated version of Podman - 4.3.0.

Can Podman Auto-update My Containers?

Yes! A popular container to run with Docker called Watchtower can automatically update containers for you when new versions are pushed to a repository. Podman can also do this via Systemd. The documentation does a good job of covering the auto-update process, but I will give you an example using podman-compose.

Open your compose file. Under any service we want auto-updated, we need to add a label. For example I will add one to my Heimdall container:

  heimdall:
    ...
    environment:
      <<: *default-tz-puid-pgid
    labels:
      - "io.containers.autoupdate=registry"
Note:
You could also add this to an extensions field if you'd like to enable auto-updates to multiple services quickly.

After adding this to your compose file, you will have to: Destroy the old container, recreate it, regenerate the Systemd unit file, and re-enable the Systemd unit file as described above.

Testing Podman Container Auto Update

Once you've accomplished that, try out a "dry-run"

podman auto-update --dry-run

Podman Auto Update
The Final Column Shows Us The Status Of Auto-Update.

If everything works as expected, you should see the above. Note the "UPDATED" column. If it reads "pending", there is an update available but it hasn't been applied yet. Removing the --dry-run flag will update the container for you and restart it. When completed, the final column should now read "true".

If the container fails to start for some reason, the default behavior is for podman to rollback the container to the previously working version.

Auto Updating Podman Containers on Schedule

If you'd like your containers to auto-update themselves on a schedule, you have 2 options: the built-in systemd unit, or you can add this command to a cron job. I'll let you discover the latter. To use the built-in systemd unit, we simply need to enable it. This unit, by default, relies on a systemd timer to run everyday at midnight and update your containers.

Enable it with:

systemctl --user enable --now podman-auto-update.service

To modify the time the unit runs or how frequently, you can edit the systemd timer files located in /usr/lib/systemd/user/podman-auto-update.timer. Better yet, before editing the files, make a copy of them into your user's .config directory we created earlier to ensure they aren't affected by updates!

Are you considering switching from Docker to Podman?

View Results

Loading ... Loading ...

Concluding Remarks

While Podman is billed as a "direct drop-in replacement" for Docker, there are still some steps involved on getting Podman to behave as expected on non-Red Hat based operating systems. I know it's taken some work to get there, but hopefully this Podman How-to has taken some of the mystery out of switching from Docker to Podman.

Podman is also updated regularly (although not in Ubuntu's repos) to become more and more in parity with Docker.

Podman-compose has also come a long way and is constantly under development to act in a predictable manner while providing compatibility with docker-compose.

These two tools took some getting used to since they are somewhat different than Docker. But as the development continues, I see Podman continuing to improve and proving easier and easier for users to migrate to. After using Podman exclusively for well over a year, I can say that it is challenging at times, but rewarding overall.

If you feel energized and want to challenge yourself, you can take our Docker Traefik guide and adapt it for Podman.

Thank you, reader, for taking the time to follow along this guide on replacing Docker with Podman. Let me know if you have any comments, suggestions, or wishes for additional posts in the comment section below! Happy hosting!

Be the 1 in 200,000. Help us sustain what we do.
114 / 150 by Dec 31, 2024
Join Us (starting from just $1.67/month)

Kristopher

Kristopher is a tech enthusiast interested in teaching and simplifying technology for others. Online privacy and responsibility has become of upmost importance and he aims to help others reduce their reliance on tech giants.

Try Deployarr