Sharing my ideas and feedback after reading “Podman for DevOps — Second Edition”
Image from the official edition
Introduction: Why I’m Never Going Back to Docker Desktop: My Switch to Podman
Containerization is no longer a “new” kid on the block. Most of us have grown up professionally with Docker and Docker Desktop as our default environment. However, after making the switch to Podman and Podman Desktop, I’ve reached a point of no return.
If you’re still on the fence about switching your local container runtime, here is why I’ve made Podman my permanent home:
- True Open Source DNA: Unlike some alternatives that have moved toward proprietary models, Podman is fully open-source. It isn’t just “free to use” — it’s built on open standards (OCI) and is seeing incredibly rapid development and innovation.
- Zero Cost, Zero Hassle: Podman and Podman Desktop are both completely free of charge. You don’t have to worry about licensing tiers or “seat” costs as your team grows; it’s enterprise-ready without the enterprise price tag.
- An Extensible Desktop Experience: Podman Desktop is a game-changer. While it is technically a separate entity from the Podman engine, it provides a seamless, open-source GUI that makes daily management a breeze. The plugin ecosystem is fantastic, allowing you to integrate with Kubernetes, OpenShift, and various AI lab tools effortlessly.
- Security by Design: One of the biggest technical wins is Podman’s “rootless” architecture. It doesn’t require a background daemon with root privileges, which significantly reduces the security surface area of your local machine.
- A Thriving Ecosystem: In my personal opinion, the community surrounding the Podman project is one of the most welcoming and active in the DevOps space right now. There is a genuine sense of momentum that makes it exciting to be a part of.
The Verdict: Docker served us well for a decade, but the flexibility, security, and “open-first” philosophy of Podman have won me over. If you haven’t tried it yet, your workflow might just thank you.
Podman for DevOps — Second Edition
In the ever-evolving landscape of software development, containerization has moved from being a niche “packaging trick” to the very foundation of modern DevOps. While Docker paved the way, a new generation of tools like Podman is redefining how we build, secure, and run these environments by focusing on native Linux standards and security.
The second edition of “Podman for DevOps” by Alessandro Arrichiello and Gianni Salinetti serves as a comprehensive guide to this ecosystem. Whether you are a system administrator, a developer, or an AI engineer, this book bridges the gap between basic container commands and production-grade orchestration.
Just a quick heads-up: I’m not affiliated with the authors or the publishing house in any way. I’m just a fan of the tool sharing my honest thoughts on the latest edition!
Also, the code samples are all from the book!
Hereafter follows a chapter-by-chapter synthesis of the book’s journey from theory to advanced AI workflows ⤵️
Part 1: From Theory to Practice: Let’s Run Our First Container with Podman
- Chapter 1: Introduction to Container Technology This chapter establishes the core primitives of containers, such as Linux namespaces and cgroups, and explains why they have become the de facto standard for cloud-readiness and microservices (maybe could be ignored if you know all this already).
- Chapter 2: Comparing Podman and Docker A critical look at architectural differences, focusing on Podman’s daemonless and rootless design versus Docker’s daemon-centric approach.
Docker is the most well-known platform in the containerization world, famous for its “all-in-one” approach to building, running, and managing containers. It relies on a central background service called a “daemon” to handle all operations, which requires root privileges on the host system. This design made Docker incredibly user-friendly and standardized the industry, but it also created a single point of failure and potential security concerns due to its requirement for high-level administrative access (I’ll come back to a comparaision of Docker vs. Buildah later).
- Chapter 3: Running the First Container A practical guide to setting up Podman across various Linux distributions, macOS, and Windows, culminating in running your first interactive container.
- Chapter 4: Managing Running Containers Beyond just running a container, this chapter teaches lifecycle management: inspecting processes, viewing logs, and the concept of “pods” — the smallest execution unit in Kubernetes.
- Chapter 5: Implementing Storage for the Container’s Data Explores how to handle data persistence using volumes and bind mounts, along with an deep dive into the
overlayfsstorage driver.
Part 2: Building Your Container from Scratch with Buildah
- Chapter 6: Meet Buildah — Building Containers from Scratch Introduces Buildah, Podman’s companion for creating images. It covers both traditional Dockerfile builds and “from scratch” workflows.
Buildah, is a specialized tool designed specifically for building container images without the need for a background daemon or root privileges. While Docker is a Swiss Army knife that handles everything, Buildah follows the Unix philosophy of “doing one thing well.” It allows developers to create images from scratch using standard shell commands or Dockerfiles, offering a more modular and secure approach. Because it doesn’t require a running daemon, Buildah is often the preferred choice for automated CI/CD pipelines where security and efficiency are top priorities.
| **Feature** | **Docker** | **Podman & Buildah** |
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| **Architecture** | **Daemon-centric**: Requires a central background process (`dockerd`) to manage containers. | **Daemonless**: Podman is a CLI tool that interacts directly with the OS and container runtimes via `conmon`. |
| **Security** | Traditionally **Rootful**: Requires root privileges to run the daemon, though rootless mode is now an option. | **Rootless by default**: Built specifically to run containers without administrative privileges. |
| **Tool Philosophy** | **Monolithic**: A single tool ("Swiss Army knife") that handles building, running, and managing images. | **Modular**: Podman runs containers; Buildah builds them; Skopeo manages image distribution. |
| **Building Images** | Uses **Dockerfiles**: The process is managed by the Docker daemon. | **Flexible**: Supports standard Dockerfiles but also allows building from scratch using shell scripts/Buildah commands. |
| **Orchestration** | Focused on **Single Containers**: Uses Docker Compose for local multi-container setups. | **Pod-centric**: Native support for "Pods" (Kubernetes units) and easy generation of Kubernetes YAML. |
| **Networking** | Managed by the **Docker Daemon** via `containerd`. | Uses **Netavark** (modern backend) or `slirp4netns` for rootless network isolation. |
| **CLI Commands** | `docker <command>` | `podman <command>` (designed as a drop-in replacement for the Docker CLI). |
An example of a Containerfile (fully compatible Dockerfile)
FROM docker.io/library/fedora
# Install required packages
RUN set -euo pipefail; \
dnf upgrade -y && \
dnf install httpd -y && \
dnf clean all -y && \
rm -rf /var/cache/dnf/*
# Custom webserver configs for rootless execution
RUN set -euo pipefail; \
sed -i 's|Listen 80|Listen 8080|' \
/etc/httpd/conf/httpd.conf; \
sed -i 's|ErrorLog "logs/error_log"|ErrorLog /dev/stderr|' \
/etc/httpd/conf/httpd.conf; \
sed -i 's|CustomLog "logs/access_log" combined|CustomLog /dev/stdout combined|' \
/etc/httpd/conf/httpd.conf; \
chown 1001 /var/run/httpd
# Copy web content
COPY index.html /var/www/html
# Define content volume
VOLUME /var/www/html
# Copy container entrypoint.sh script
COPY entrypoint.sh /entrypoint.sh
# Declare exposed ports
EXPOSE 8080
# Declare default user
USER 1001
ENTRYPOINT ["/entrypoint.sh"]
CMD ["httpd"]
- Chapter 7: Integrating with Existing Application Build Processes Focuses on advanced build strategies, including multistage builds and running Buildah inside containers for CI/CD pipelines.
Sample Go App
package main
import (
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
w.Header().Set("Content-Type", "text/html")
w.Write([]byte("<html>\n<body>\n"))
w.Write([]byte("<p>Hello World!</p>\n"))
w.Write([]byte("</body>\n</html>\n"))
}
func main() {
http.HandleFunc("/", handler)
log.Println("Starting http server")
log.Fatal(http.ListenAndServe(":8080", nil))
}
The build commands
#!/bin/bash
# Define builder and runtime images
BUILDER=docker.io/library/golang
RUNTIME=registry.access.redhat.com/ubi9/ubi-micro:latest
RELEASE=1.0.0
# Create builder container
container1=$(buildah from $BUILDER)
# Copy files from host
if [ -f go.mod ]; then
buildah copy $container1 'go.mod' '/go/src/hello-world/'
else
exit 1
fi
if [ -f main.go ]; then
buildah copy $container1 'main.go' '/go/src/hello-world/'
else
exit 1
fi
# Configure and start build
buildah config --workingdir /go/src/hello-world $container1
buildah run $container1 go get -d -v ./...
buildah run $container1 go build -v ./...
# Extract build artifact and create a version archive
buildah unshare --mount mnt=$container1 \
sh -c 'cp $mnt/go/src/hello-world/hello-world .'
cat > README << EOF
Version $RELEASE release notes:
- Implement basic features
EOF
tar zcf hello-world-${RELEASE}.tar.gz hello-world README
rm -f hello-world README
# Create runtime container
container2=$(buildah from $RUNTIME)
# Copy files from the builder container
buildah copy --chown=1001:1001 \
--from=$container1 $container2 \
'/go/src/hello-world/hello-world' '/'
# Configure exposed ports
buildah config --port 8080 $container2
# Configure default CMD
buildah config --cmd /hello-world $container2
# Configure default user
buildah config--user=1001 $container2
# Commit final image
buildah commit $container2 helloworld:$RELEASE
# Remove build containers
buildah rm $container1 $container2
- Chapter 8: Choosing the Container Base Image Provides best practices for selecting secure base images, introducing Red Hat’s Universal Base Image (UBI) as a reliable foundation.
Example of ubi8-minimal-http-server
# Based on the UBI8 Minimal image
FROM registry.access.redhat.com/ubi8-minimal
# Upgrade and install Python 3.6
RUN microdnf upgrade && microdnf install python3
# Copy source code
COPY entrypoint.sh http_server.py /
# Expose the default httpd port 80
EXPOSE 8080
# Configure the container entrypoint
ENTRYPOINT ["/entrypoint.sh"]
# Run the httpd
CMD ["/usr/bin/python3", "-u", "/http_server.py"]
- Chapter 9: Pushing Images to a Container Registry Explains how to manage and distribute images using registries and the Skopeo tool for remote image inspection and copying.
Skopeo is a specialized, open-source command-line utility designed for the inspection and manipulation of container images and registries. Unlike Podman or Docker, Skopeo does not run containers; instead, it provides a lightweight way to perform tasks such as copying images between different types of registries (e.g., from a local directory to a remote mirror) and inspecting remote image manifests without needing to pull the entire image locally. It is a critical component of the Podman and Buildah ecosystem, often used by DevOps teams to manage image distribution, synchronize registries, and handle image signing or verification.
- Chapter 10: Securing Containers A deep dive into security, covering rootless execution, image signing with Sigstore, Linux kernel capabilities, and SELinux integration.
Example of a rootless Nginx image from docker.io
FROM docker.io/library/nginx:mainline-alpine
RUN rm /etc/nginx/conf.d/*
ADD hello-podman.conf /etc/nginx/conf.d/
RUN chmod -R a+w /var/cache/nginx/ \
&& touch /var/run/nginx.pid \
&& chmod a+w /var/run/nginx.pid
EXPOSE 8080
USER nginx
Part 3: Managing and Integrating Containers Securely
- Chapter 11: Troubleshooting and Monitoring Containers Equips readers with tools like nsenter and health checks to diagnose and fix failures in running containers or build processes.
- Chapter 12: Implementing Container Networking Concepts Introduces the Netavark network backend, explaining how containers communicate, how DNS resolution works, and the nuances of rootless networking.
- Chapter 13: Docker Migration Tips and Tricks A practical guide for those switching from Docker, covering CLI compatibility, command aliases, and Docker Compose integration.
Simple Go / Redis Application
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"github.com/go-redis/redis"
"github.com/gorilla/mux"
)
// User is the object stored in Redis
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Id string `json:"id"`
}
var user User
var redisHost = os.Getenv("REDIS_HOST")
var client = redis.NewClient(&redis.Options{
Addr: redisHost + ":6379",
Password: "",
DB: 0,
})
// handePost handles HTTP POST requests
func handlePost(w http.ResponseWriter, r *http.Request) {
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
json, err := json.Marshal(user)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = client.Set(user.Id, json, 0).Err()
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Println("Storing data: ", string(json))
}
// handleGet handles HTTP GET requests
func handleGet(w http.ResponseWriter, r *http.Request) {
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
val, err := client.Get(user.Id).Result()
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, val)
log.Println("Retrieving data: ", val)
}
func main() {
defer client.Close()
r := mux.NewRouter()
r.HandleFunc("/", handlePost).Methods("POST")
r.HandleFunc("/", handleGet).Methods("GET")
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
log.Fatal(srv.ListenAndServe())
}
The Dockerfile
FROM docker.io/library/golang AS builder
# Copy files for build
RUN mkdir -p /go/src/golang-redis
COPY go.mod main.go /go/src/golang-redis
# Set the working directory
WORKDIR /go/src/golang-redis
# Download dependencies
RUN go get -d -v ./...
# Install the package
RUN go build -v
# Runtime image
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest as bin
COPY --from=builder /go/src/golang-redis/golang-redis /usr/local/bin
COPY entrypoint.sh /
EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
- Chapter 14: Interacting with systemd and Kubernetes Shows how to treat containers as native system services using Quadlets and how to export Podman configurations into Kubernetes YAML resources.
A multipod Wordpress YAML file
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-4.0.0-rc4
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
volume.podman.io/driver: local
creationTimestamp: "2022-02-13T21:32:35Z"
name: dbvol
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
status: {}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
volume.podman.io/driver: local
creationTimestamp: "2022-02-13T21:32:35Z"
name: wpvol
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
status: {}
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2022-02-13T21:32:35Z"
labels:
app: wordpress-pod
name: wordpress-pod
spec:
ports:
- name: "80"
nodePort: 30408
port: 80
targetPort: 80
selector:
app: wordpress-pod
type: NodePort
---
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2022-02-13T21:32:35Z"
labels:
app: mysql-pod
name: mysql-pod
spec:
ports:
- name: "3306"
nodePort: 30284
port: 3306
targetPort: 3306
selector:
app: mysql-pod
type: NodePort
---
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2022-02-13T21:32:35Z"
labels:
app: wordpress-pod
name: wordpress-pod
spec:
containers:
- args:
- apache2-foreground
env:
- name: WORDPRESS_DB_NAME
value: wordpress
- name: WORDPRESS_DB_HOST
value: mysql-pod
- name: WORDPRESS_DB_PASSWORD
value: wordpress
- name: WORDPRESS_DB_USER
value: wordpress
image: docker.io/library/wordpress:latest
name: wordpress
ports:
- containerPort: 80
hostPort: 8080
resources: {}
securityContext:
capabilities:
drop:
- CAP_MKNOD
- CAP_NET_RAW
- CAP_AUDIT_WRITE
volumeMounts:
- mountPath: /var/www/html
name: wpvol-pvc
restartPolicy: Never
volumes:
- name: wpvol-pvc
persistentVolumeClaim:
claimName: wpvol
status: {}
---
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2022-02-13T21:32:35Z"
labels:
app: mysql-pod
name: mysql-pod
spec:
containers:
- args:
- mysqld
env:
- name: MYSQL_ROOT_PASSWORD
value: myrootpasswd
- name: MYSQL_DATABASE
value: wordpress
- name: MYSQL_USER
value: wordpress
- name: MYSQL_PASSWORD
value: wordpress
image: docker.io/library/mysql:latest
name: db
ports:
- containerPort: 3306
hostPort: 3306
resources: {}
securityContext:
capabilities:
drop:
- CAP_MKNOD
- CAP_NET_RAW
- CAP_AUDIT_WRITE
volumeMounts:
- mountPath: /var/lib/mysql
name: dbvol-pvc
restartPolicy: Never
volumes:
- name: dbvol-pvc
persistentVolumeClaim:
claimName: dbvol
status: {}
#!/bin/bash
#
# This script can be used to prepare a WordPress multi pod environment that
# can be used to test the podman generate kube command.
#
# Create the rootless network
podman network create kubenet
# Create the volumes
for vol in wpvol dbvol; do podman volume create $vol; done
# Create the MySQL pod and its related container
podman pod create -p 3306:3306 --name mysql-pod --network kubenet
podman create --name db -v dbvol:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=myrootpasswd -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wordpress -e MYSQL_PASSWORD=wordpress --pod mysql-pod docker.io/library/mysql
# Create the WordPress pod and its related container
podman pod create -p 8080:80 --name wordpress-pod --network kubenet
podman create --name wordpress --pod wordpress-pod -v wpvol:/var/www/html -e WORDPRESS_DB_HOST=mysql-pod -e WORDPRESS_DB_USER=wordpress -e WORDPRESS_DB_PASSWORD=wordpress -e WORDPRESS_DB_NAME=wordpress docker.io/library/wordpress
# Start the pods
podman pod start mysql-pod
podman pod start wordpress-pod
- Chapter 15: Managing Your Container, Kubernetes, and AI Workloads from a Graphical Interface Introduces Podman Desktop and the Podman AI Lab, demonstrating how to manage complex local AI models and chatbots through a user-friendly interface.
A word on “Podman AI Lab”
A major focus of this chapter is the Podman AI Lab, an extension that brings AI development to the local environment:
- Local Inference: Users can provision local inference servers to run AI models without relying on cloud providers.
- Model Integration: It demonstrates how to obtain API endpoints from these local models to integrate them into applications.
- Practical Use Case: The chapter provides a step-by-step guide to building a chatbot, which includes starting an OpenAI-compatible inference server and connecting it to an application container like AnythingLLM.
Conclusion
In summary, I believe this book serves as an excellent gateway for “techies” and DevOps enthusiasts who have yet to fully explore the Podman ecosystem. It effectively bridges the gap between basic container knowledge and production-ready workflows.
However, a quick note for the veterans: if you are already a hands-on expert deeply entrenched in the daily nuances of Podman, Docker, and container orchestration, you might find this a “nice to read” refresher rather than a “must-have” deep dive.
That said, I highly recommend it for anyone looking to formalize their understanding of why the industry is shifting. The book does a fantastic job of articulating the core advantages of Podman — its commitment to open-source principles, its superior security architecture, and its rapidly expanding ecosystem. It’s a solid resource for anyone ready to level up their container game.
Links
- Containers GitHub: https://github.com/containers
- Podman: https://github.com/containers/podman
- Buildah: https://github.com/containers/buildah
- Skopeo: https://github.com/containers/skopeo
- Podman Desktop: https://github.com/podman-desktop/podman-desktop
- The Book on PacktPub site: https://www.packtpub.com/en-fr/product/podman-for-devops-9781835886632
- Book’s repository on GitHub: https://github.com/PacktPublishing/Podman-for-DevOps-Second-Edition



Top comments (0)