At many places you can read that Podman is a drop-in replacement for Docker. But is it as easy as it sounds? In this blog, you will start with a production-ready Dockerfile and execute the Podman commands just like you would do when using Docker. Let’s investigate whether this works without any problems!

1. Introduction

Podman is a container engine just as Docker is. Podman, however, is a daemonless container engine and it runs containers by default as rootless containers. This is more secure than running containers as root. The Docker daemon can also run as a non-root user nowadays.

Podman advertises on their website that Podman is a drop-in replacement for Docker. Just add alias docker=podman and you will be fine. Let’s investigate whether it is that simple. In the remainder of this blog, you will try to build a production-ready Dockerfile for running a Spring Boot application. You will run it as a single container and you will try to run two containers and have some intercontainer communication. In the end, you will verify how volumes can be mounted.

One of the prerequisites for this blog is using a Linux operating system. Podman is not available for Windows.

The sources used in this blog, can be found at GitHub.

The Dockerfile you will be using, runs a Spring Boot application. It is a basic Spring Boot application containing one controller which returns a hello message.

Build the jar:

$ mvn clean verify

Run the jar:

$ java -jar target/mypodmanplanet-0.0.1-SNAPSHOT.jar 

Check the endpoint:

$ curl http://localhost:8080/hello
Hello Podman!

The Dockerfile is based on a previous blog about Docker best practices. The file 1-Dockerfile-starter can be found in the Dockerfiles directory.

FROM eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f AS builder
WORKDIR application
ARG JAR_FILE
COPY target/${JAR_FILE} app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f
WORKDIR /opt/app
RUN addgroup --system javauser && adduser -S -s /usr/sbin/nologin -G javauser javauser
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
RUN chown -R javauser:javauser .
USER javauser

ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

2. Prerequisites

Prerequisites for this blog are:

  • Basic Linux knowledge, Ubuntu 22.04 is used during this post;
  • Basic Java and Spring Boot knowledge;
  • Basic Docker knowledge;

3. Installation

Installing Podman is quite easy. Just run the following command.

$ sudo apt-get install podman

Verify correct installation.

$ podman --version
podman version 3.4.4

You can also install podman-docker, which will create an alias when you use docker in your commands. It is advised to wait for the conclusion of this post before you install this one.

4. Build Dockerfile

First thing to do, is to build the container image. Execute from the root of the repository the following command.

$ podman build . --tag mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT -f Dockerfiles/1-Dockerfile-starter --build-arg JAR_FILE=mypodmanplanet-0.0.1-SNAPSHOT.jar
[1/2] STEP 1/5: FROM eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f AS builder
[2/2] STEP 1/10: FROM eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f
Error: error creating build container: short-name "eclipse-temurin@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f" did not resolve to an alias and no unqualified-search registries are defined in "/etc/containers/registries.conf"

This returns an error while retrieving the base image. The error message refers to /etc/containers/registries.conf. The following is stated in this file.

# For more information on this configuration file, see containers-registries.conf(5).
#
# NOTE: RISK OF USING UNQUALIFIED IMAGE NAMES
# We recommend always using fully qualified image names including the registry
# server (full dns name), namespace, image name, and tag
# (e.g., registry.redhat.io/ubi8/ubi:latest). Pulling by digest (i.e.,
# quay.io/repository/name@digest) further eliminates the ambiguity of tags.
# When using short names, there is always an inherent risk that the image being
# pulled could be spoofed. For example, a user wants to pull an image named
# `foobar` from a registry and expects it to come from myregistry.com. If
# myregistry.com is not first in the search list, an attacker could place a
# different `foobar` image at a registry earlier in the search list. The user
# would accidentally pull and run the attacker's image and code rather than the
# intended content. We recommend only adding registries which are completely
# trusted (i.e., registries which don't allow unknown or anonymous users to
# create accounts with arbitrary names). This will prevent an image from being
# spoofed, squatted or otherwise made insecure.  If it is necessary to use one
# of these registries, it should be added at the end of the list.

To conclude with, it is suggested to use a fully qualified image name. This means that you need to change the lines containing:

eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f

into:

docker.io/eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f

You just add docker.io/ to the image name. A minor change, but already one difference compared to Docker.

The image name is fixed in file 2-Dockerfile-fix-shortname, so let’s try building the image again.

$ podman build . --tag mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT -f Dockerfiles/2-Dockerfile-fix-shortname --build-arg JAR_FILE=mypodmanplanet-0.0.1-SNAPSHOT.jar
[1/2] STEP 1/5: FROM docker.io/eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f AS builder
Trying to pull docker.io/library/eclipse-temurin@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f...
Getting image source signatures
Copying blob 72ac8a0a29d6 done  
Copying blob f56be85fc22e done  
Copying blob f8ed194273be done  
Copying blob e5daea9ee890 done  
[2/2] STEP 1/10: FROM docker.io/eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f
Error: error creating build container: writing blob: adding layer with blob "sha256:f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09": Error processing tar file(exit status 1): potentially insufficient UIDs or GIDs available in user namespace (requested 0:42 for /etc/shadow): Check /etc/subuid and /etc/subgid: lchown /etc/shadow: invalid argument

Now there is an error about potentially insufficient UIDs or GIDs available in the user namespace. More information about this error can be found here. It is very well explained in that post, it is too much to repeat all of this in this post. The summary is that the image which is trying to be pulled, has files owned by UIDs over 65.536. Due to that issue, the image would not fit into rootless Podman’s default UID mapping, which limits the number of UIDs and GIDs available.

So, how to solve this?

First check the contents of /etc/subuid and /etc/subgid. In my case, the following is output, for you it will probably be different.

$ cat /etc/subuid
admin:100000:65536
$ cat /etc/subgid
admin:100000:65536

The admin user listed in the output has 100.000 as the first UID or GID available and it has a size of 65.536. The format is user:start:size. This means that the admin user has access to UIDs or GIDs 100.000 up to and including 165.535.

My current user is not listed here and that means that my user can only allocate 1 UID en 1 GID for the container. That 1 UID/GID is already taken for the root user in the container. If a container image needs an extra user, there will be a problem, as you can see above.

This can be solved by adding UIDs en GIDs for your user. Let’s add values 200.000 up to and including 265.535 to your user.

$ sudo usermod --add-subuids 200000-265535 --add-subgids 200000-265535 <replace with your user>

Verify the contents of both files again. The user is added to both files.

$ cat /etc/subgid
admin:100000:65536
<your user>:200000:65536
$ cat /etc/subuid
admin:100000:65536
<your user>:200000:65536

Secondly, you need to run the following command.

$ podman system migrate

Try to build the image again and now it works.

$ podman build . --tag mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT -f Dockerfiles/2-Dockerfile-fix-shortname --build-arg JAR_FILE=mypodmanplanet-0.0.1-SNAPSHOT.jar
[1/2] STEP 1/5: FROM docker.io/eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f AS builder
Trying to pull docker.io/library/eclipse-temurin@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f...
Getting image source signatures
Copying blob f56be85fc22e done  
Copying blob f8ed194273be done  
Copying blob 72ac8a0a29d6 done  
Copying blob e5daea9ee890 done  
Copying config c74d412c3d done  
Writing manifest to image destination
Storing signatures
[1/2] STEP 2/5: WORKDIR application
--> d4f0e970dc1
[1/2] STEP 3/5: ARG JAR_FILE
--> ca97dcd6f2a
[1/2] STEP 4/5: COPY target/${JAR_FILE} app.jar
--> 58d88cfa511
[1/2] STEP 5/5: RUN java -Djarmode=layertools -jar app.jar extract
--> 348cae813a4
[2/2] STEP 1/10: FROM docker.io/eclipse-temurin:17.0.6_10-jre-alpine@sha256:c26a727c4883eb73d32351be8bacb3e70f390c2c94f078dc493495ed93c60c2f
[2/2] STEP 2/10: WORKDIR /opt/app
--> 4118cdf90b5
[2/2] STEP 3/10: RUN addgroup --system javauser && adduser -S -s /usr/sbin/nologin -G javauser javauser
--> cd11f346381
[2/2] STEP 4/10: COPY --from=builder application/dependencies/ ./
--> 829bffcb6c7
[2/2] STEP 5/10: COPY --from=builder application/spring-boot-loader/ ./
--> 2a93f97d424
[2/2] STEP 6/10: COPY --from=builder application/snapshot-dependencies/ ./
--> 3e292cb0456
[2/2] STEP 7/10: COPY --from=builder application/application/ ./
--> 5dd231c5b51
[2/2] STEP 8/10: RUN chown -R javauser:javauser .
--> 4d736e8c3bb
[2/2] STEP 9/10: USER javauser
--> d7a96ca6f36
[2/2] STEP 10/10: ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
[2/2] COMMIT mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT
--> 567fd123071
Successfully tagged localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT
567fd1230713f151950de7151da82a19d34f80af0384916b13bf49ed72fd2fa1

Verify the list of images with Podman just like you would do with Docker:

$ podman images
REPOSITORY                                  TAG             IMAGE ID      CREATED        SIZE
localhost/mydeveloperplanet/mypodmanplanet  0.0.1-SNAPSHOT  567fd1230713  2 minutes ago  209 MB

Is Podman a drop-in replacement for Docker for building a Dockerfile?

No, it is not a drop-in replacement because you needed to use the fully qualified image name for the base image in the Dockerfile and you needed to make changes to the user namespace in order to be able to pull the image. Besides these two changes, building the container image just worked.

5. Start Container

Now that you have build the image, it is time to start a container.

$ podman run --name mypodmanplanet -d localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT

The container has started successfully.

$ podman ps
CONTAINER ID  IMAGE                                                      COMMAND     CREATED         STATUS             PORTS       NAMES
27639dabb573  localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT              18 seconds ago  Up 18 seconds ago              mypodmanplanet

You can also inspect the container logs.

$ podman logs mypodmanplanet

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.5)

2023-04-22T14:38:05.896Z  INFO 1 --- [           main] c.m.m.MyPodmanPlanetApplication          : Starting MyPodmanPlanetApplication v0.0.1-SNAPSHOT using Java 17.0.6 with PID 1 (/opt/app/BOOT-INF/classes started by javauser in /opt/app)
2023-04-22T14:38:05.898Z  INFO 1 --- [           main] c.m.m.MyPodmanPlanetApplication          : No active profile set, falling back to 1 default profile: "default"
2023-04-22T14:38:06.803Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-04-22T14:38:06.815Z  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-04-22T14:38:06.816Z  INFO 1 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.7]
2023-04-22T14:38:06.907Z  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-04-22T14:38:06.910Z  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 968 ms
2023-04-22T14:38:07.279Z  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-04-22T14:38:07.293Z  INFO 1 --- [           main] c.m.m.MyPodmanPlanetApplication          : Started MyPodmanPlanetApplication in 1.689 seconds (process running for 1.911)

Verify whether the endpoint can be accessed.

$ curl http://localhost:8080/hello
curl: (7) Failed to connect to localhost port 8080 after 0 ms: Connection refused

That’s not the case. With Docker, you can inspect the container to see which IP address is allocated to the container.

$ podman inspect mypodmanplanet | grep IPAddress
            "IPAddress": "",

It seems that the container does not have a specific IP address. The endpoint is also not accessible at localhost.

The solution is to add a port mapping when creating the container.

Stop the container and remove it.

$ podman stop mypodmanplanet 
mypodmanplanet
$ podman rm mypodmanplanet 
27639dabb5730d3244d205200a409dbc3a1f350196ba238e762438a4b318ef73

Start the container again, but this time with a port mapping of internal port 8080 to external port 8080.

$ podman run -p 8080:8080 --name mypodmanplanet -d localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT

Verify again whether the endpoint can be accessed. This time it works.

$ curl http://localhost:8080/hello
Hello Podman!

Stop and remove the container before continuing this blog.

Is Podman a drop-in replacement for Docker for running a container image?

No, it is not a drop-in replacement. Although it was possible to use exactly the same commands as with Docker, you needed to explicitely add a port mapping. Without the port mapping, it was not possible to access the endpoint.

6. Volume Mounts

Volume mounts and access of directories and files outside the container and inside a container often leads to Permission Denied errors. In a previous blog, this behaviour is extensively described for the Docker engine. It is interesting to see how this works when using Podman.

You will map an application.properties file in the container next to the jar-file. The Spring Boot application will pick up this application.properties file. The file configures the server port to port 8082 and the file is located in directory properties in the root of the repository.

server.port=8082

Run the container with a port mapping from internal port 8082 to external port 8083 and mount the application.properties file into the container directory /opt/app where also the jar-file is located. The volume mount has the property ro in order to indicate that it is a read-only file.

$ podman run -p 8083:8082 --volume ./properties/application.properties:/opt/app/application.properties:ro --name mypodmanplanet localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT

Verify whether the endpoint can be accessed and it just works.

$ curl http://localhost:8083/hello
Hello Podman!

Open a shell in the container and list the directory contents in order to view the ownership of the file.

$ podman exec -it mypodmanplanet sh
/opt/app $ ls -la
total 24
drwxr-xr-x    1 javauser javauser      4096 Apr 15 10:33 .
drwxr-xr-x    1 root     root          4096 Apr  9 12:57 ..
drwxr-xr-x    1 javauser javauser      4096 Apr  9 12:57 BOOT-INF
drwxr-xr-x    1 javauser javauser      4096 Apr  9 12:57 META-INF
-rw-r--r--    1 root     root            16 Apr 15 10:24 application.properties
drwxr-xr-x    1 javauser javauser      4096 Apr  9 12:57 org

With Docker, the file would have been owned by your local system user, but with Podman, the file is owned by root. Let’s check the permissions of the file on the local system.

$ ls -la
total 12
drwxr-xr-x 2 <myuser> domain users 4096 apr 15 12:24 .
drwxr-xr-x 8 <myuser> domain users 4096 apr 15 12:24 ..
-rw-r--r-- 1 <myuser> domain users   16 apr 15 12:24 application.properties

As you can see, the file on the local system is owned by <myuser>. This means that your host user, who is running the container, is seen as user root inside of the container.

Open a shell in the container and try to change the contents of file application.properties. You will notice that this is not allowed because you are user javauser.

$ podman exec -it mypodmanplanet sh
/opt/app $ vi application.properties 
/opt/app $ whoami
javauser

Stop and remove the container.

Run the container, but this time with property U instead of ro. The U suffix tells Podman to use the correct host UID and GID based on the UID and GID within the container, to change recursively the owner and group of the source volume.

$ podman run -p 8083:8082 --volume ./properties/application.properties:/opt/app/application.properties:U --name mypodmanplanet localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT

Open a shell in the container and now user javauser is owner of the file.

$ podman exec -it mypodmanplanet sh
/opt/app $ ls -la
total 24
drwxr-xr-x    1 javauser javauser      4096 Apr 15 10:41 .
drwxr-xr-x    1 root     root          4096 Apr  9 12:57 ..
drwxr-xr-x    1 javauser javauser      4096 Apr  9 12:57 BOOT-INF
drwxr-xr-x    1 javauser javauser      4096 Apr  9 12:57 META-INF
-rw-r--r--    1 javauser javauser        16 Apr 15 10:24 application.properties
drwxr-xr-x    1 javauser javauser      4096 Apr  9 12:57 org

On the local system, a different UID and GID than my local user has taken ownership.

$ ls -la properties/
total 12
drwxr-xr-x 2  <myuser> domain  users 4096 apr 15 12:24 .
drwxr-xr-x 8  <myuser> domain  users 4096 apr 15 12:24 ..
-rw-r--r-- 1    200099        200100   16 apr 15 12:24 application.properties

This time, changing the file on the local system is not allowed, but it is allowed inside the container for user javauser.

Is Podman a drop-in replacement for Docker for mounting volumes inside a container?

No, it is not a drop-in replacement. File permissions functions a bit different than with the Docker engine. You need to know the differences in order to be able to mount files and directories inside containers.

7. Pod

Podman knows the concept of a Pod, just like a Pod in Kubernetes. A Pod allows you to group containers. A Pod also has a shared network name space and this means that containers inside a Pod can connect to each other. More information about container networking can be found here. This means that Pods are the first choice for grouping containers. When using Docker, you will use Docker Compose for this. There exists something like Podman Compose, but this deserves a blog on itself.

Let’s see how this works. You will set up a Pod running two containers with the Spring Boot applicaton.

First you need to create a Pod. You also need to expose the ports you want to be accessible outside of the Pod. This can be done with the -p argument. And you give the Pod a name, hello-pod in this case.

$ podman pod create -p 8080-8081:8080-8081 --name hello-pod

When you list the Pod, you notice that it already contains one container. This is the infra container. This infra container holds the name space in order that containers can connect to each other and it enables starting and stopping containers in the Pod. The infra container is based on the k8s.gcr.io/pause image.

$ podman pod ps
POD ID        NAME        STATUS      CREATED        INFRA ID      # OF CONTAINERS
dab9029ad0c5  hello-pod   Created     3 seconds ago  aac3420b3672  1
$ podman ps --all
CONTAINER ID  IMAGE                 COMMAND     CREATED        STATUS      PORTS                             NAMES
aac3420b3672  k8s.gcr.io/pause:3.5              4 minutes ago  Created     0.0.0.0:8080-8081->8080-8081/tcp  dab9029ad0c5-infra

Create a container mypodmanplanet-1 and add it to the Pod. By means of the --env argument, you change the port of the Spring Boot application to port 8081.

$ podman create --pod hello-pod --name mypodmanplanet-1 --env 'SERVER_PORT=8081' localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT env

Start the Pod.

$ podman pod start hello-pod

Verify whether the endpoint can be reached at port 8081 and verify that the endpoint at port 8080 cannot be reached.

$ curl http://localhost:8081/hello
Hello Podman!
$ curl http://localhost:8080/hello
curl: (56) Recv failure: Connection reset by peer

Add a second container mypodmanplanet-2 to the Pod, this time running at the default port 8080.

$ podman create --pod hello-pod --name mypodmanplanet-2 localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT

Verify the Pod status. It says that the status is Degraded.

$ podman pod ps
POD ID        NAME        STATUS      CREATED        INFRA ID      # OF CONTAINERS
dab9029ad0c5  hello-pod   Degraded    9 minutes ago  aac3420b3672  3

Take a look at the containers. Two containers are running, the new container is just created. That is the reason the Pod has status Degraded.

$ podman ps --all
CONTAINER ID  IMAGE                                                      COMMAND     CREATED             STATUS            PORTS                             NAMES
aac3420b3672  k8s.gcr.io/pause:3.5                                                   11 minutes ago      Up 2 minutes ago  0.0.0.0:8080-8081->8080-8081/tcp  dab9029ad0c5-infra
321a62fbb4fc  localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT  env         3 minutes ago       Up 2 minutes ago  0.0.0.0:8080-8081->8080-8081/tcp  mypodmanplanet-1
7b95fb521544  localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT              About a minute ago  Created           0.0.0.0:8080-8081->8080-8081/tcp  mypodmanplanet-2

Start the second container and verify the Pod status, the status is now Running.

$ podman start mypodmanplanet-2
$ podman pod ps
POD ID        NAME        STATUS      CREATED         INFRA ID      # OF CONTAINERS
dab9029ad0c5  hello-pod   Running     12 minutes ago  aac3420b3672  3

Both endpoints can now be reached.

$ curl http://localhost:8080/hello
Hello Podman!
$ curl http://localhost:8081/hello
Hello Podman!

Verify whether you can access the endpoint of container mypodmanplanet-1 from within mypodmanplanet-2. This also works.

$ podman exec -it mypodmanplanet-2 sh
/opt/app $ wget http://localhost:8081/hello
Connecting to localhost:8081 (127.0.0.1:8081)
saving to 'hello'
hello                100% |***********************************************************************************************************************************|    13  0:00:00 ETA
'hello' saved

8. Cleanup

To conclude with, you can do some cleanup.

Stop the running Pod.

$ podman pod stop hello-pod

The Pod has status Exited now.

$ podman pod ps
POD ID        NAME        STATUS      CREATED         INFRA ID      # OF CONTAINERS
dab9029ad0c5  hello-pod   Exited      55 minutes ago  aac3420b3672  3

All containers in the Pod are also exited.

$ podman ps --all
CONTAINER ID  IMAGE                                                      COMMAND     CREATED         STATUS                           PORTS                             NAMES
aac3420b3672  k8s.gcr.io/pause:3.5                                                   56 minutes ago  Exited (0) About a minute ago    0.0.0.0:8080-8081->8080-8081/tcp  dab9029ad0c5-infra
321a62fbb4fc  localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT  env         48 minutes ago  Exited (143) About a minute ago  0.0.0.0:8080-8081->8080-8081/tcp  mypodmanplanet-1
7b95fb521544  localhost/mydeveloperplanet/mypodmanplanet:0.0.1-SNAPSHOT              46 minutes ago  Exited (143) About a minute ago  0.0.0.0:8080-8081->8080-8081/tcp  mypodmanplanet-2

Remove the Pod.

$ podman pod rm hello-pod

The Pod and the containers are removed.

$ podman pod ps
POD ID      NAME        STATUS      CREATED     INFRA ID    # OF CONTAINERS
$ podman ps --all
CONTAINER ID  IMAGE       COMMAND     CREATED     STATUS      PORTS       NAMES

9. Conclusion

The bold statement that Podman is a drop-in replacement for Docker is not true. Podman differs from Docker on certain topics like building container images, starting containers, networking, volume mounts, intercontainer communication, etc. However, Podman does support many Docker commands. The statement should be Podman is an alternative for Docker. This is certainly true. It is important for you to know and understand the differences before switching to Podman. After this, it is definitely a good alternative.