Docker 73 🐳 Podman Desktop, Rootless Pods, and Generating Kubernetes Manifests from Pods
Podman Desktop is the graphical interface that turns Podman from a command-line tool into a complete development environment. It installs and configures Podman, provides a dashboard for containers, images, pods, and volumes, and integrates with Kubernetes through Kind, Minikube, and OpenShift . The tool is available on Linux, macOS, and Windows, and it joined the CNCF in January 2025 alongside other Red Hat container projects . For developers on RHEL 10, Podman Desktop is available directly from the RHEL extensions repository, and for macOS and Windows users it runs through a lightweight Linux VM managed by podman machine .
The rootless pod is the feature that makes Podman’s local development environment match Kubernetes’ production topology. A Podman pod groups one or more containers under a shared network namespace, with an infrastructure container (infra) that holds the ports and the network configuration . The pod itself can be run rootless, meaning the containers inside run with the invoking user’s UID mapped to root inside the user namespace, and the host never grants the pod root privileges . This is the same model that Kubernetes uses with pod security contexts, and it is the reason podman generate kube can produce a manifest that runs on a cluster without modification.
The manifest generation is the bridge between local development and cluster deployment. podman kube generate (the modern replacement for podman generate kube) reads a running pod or container and produces Kubernetes YAML . The generated YAML includes the pod spec, the container specs, the port mappings, and the volume mounts. Bind-mounted volumes become hostPath volumes; named volumes become persistentVolumeClaim references . The output can be applied directly with kubectl apply -f, or replayed locally with podman kube play .
Key point: Podman Desktop provides the GUI; rootless pods group containers under a shared network namespace with the invoking user’s UID mapped to root; podman kube generate converts a running pod into Kubernetes YAML that can be deployed to a cluster or replayed locally.
Why Podman Desktop and rootless pods matter
The GUI gap problem. Podman’s CLI is Docker-compatible, but a CLI alone is not a development environment. Podman Desktop fills the gap with a dashboard for images, containers, pods, and volumes, a system tray for checking engine status, and extensions for Kubernetes clusters, AI models, and bootable containers . The tool detects an existing Podman installation and configures it automatically, so the transition from Docker Desktop is not a reinstallation of the container engine—it is a change of the interface .
The rootless security problem. Docker Desktop runs its daemon as root and requires elevated privileges to install and operate. Podman runs rootless by default: the container’s root is mapped to the user’s UID on the host, and a compromised container is limited to the user’s permissions . On Windows, this means Podman containers do not require administrator privileges, and there is no daemon process running in the background waiting to be exploited . The security improvement is real, but it has consequences: bind mounts that Docker allowed silently may fail under Podman because the UID mapping means the container cannot write to a directory owned by a different UID .
The pod topology problem. Docker has no native pod concept. A developer who wants to test a multi-container application on Docker must either use Compose or manually create a network and connect the containers. Podman’s pods are the same abstraction that Kubernetes uses: containers in a pod share a network namespace, can communicate over localhost, and can share volumes . This makes the local development environment match the production environment, which is the entire point of using containers for development in the first place.
The manifest drift problem. A Compose file describes a local development environment. A Kubernetes manifest describes a production deployment. The two are similar but not identical, and the differences are the source of the “works on my machine” failures that plague container deployments. podman kube generate eliminates the drift by producing the Kubernetes manifest directly from the running pod. The manifest is not a translation of a Compose file; it is a description of what is actually running .
a. Podman Desktop: the GUI and the extensions
Podman Desktop is not a single application. It is a shell that hosts extensions. The core provides the dashboard, the container and pod management, and the integration with the Podman engine. The extensions provide Kubernetes cluster creation (Kind, Minikube), AI model management (Podman AI Lab), bootable container images, and RHEL VM management .
The Docker compatibility feature is the transition mechanism. Podman Desktop can run docker commands, interpret Dockerfiles, and use Compose files. The documentation describes this as “Docker compatibility” and positions it as the way to migrate from Docker without changing the workflow . The CLI alias (alias docker=podman) is the command-line equivalent, and for standard Dockerfiles and Compose configurations, the migration is measured in days, not weeks .
The Kubernetes integration is the differentiator. Podman Desktop can create a local cluster with Kind or Minikube, deploy the generated YAML to it, and inspect the running pods from the same dashboard. The workflow is: create a pod locally with podman pod create and podman run --pod, generate the YAML with podman kube generate, and deploy it to the cluster with kubectl apply . This is the local-to-cluster path that Docker Desktop’s Kubernetes integration approximates but does not fully match, because Docker Desktop’s Kubernetes runs inside the Docker VM, not as a separate cluster that the developer can inspect.
b. Rootless pods: the user namespace mapping
A rootless pod is created with podman pod create under the invoking user’s account. The pod’s containers run with the user’s UID mapped to root inside the user namespace. The default mapping is "" (empty value), which maps the host user to the container’s root user . The --userns=keep-id option maps the host UID to the same UID inside the container, which is useful when the container needs to read and write files owned by the host user without changing the ownership .
# Create a rootless pod with two containers
podman pod create --name myapp -p 8080:80
# Run the containers inside the pod
podman run -d --pod myapp --name web nginx:latest
podman run -d --pod myapp --name redis redis:latest
# The pod's infra container holds the network namespace and the port mapping
# The containers share localhost and can communicate directly
The rootless pod’s volume mounts are the most common source of friction. A bind mount from a host directory owned by root will be unwritable by the container’s root, because the container’s root is the host user’s UID. On SELinux-enabled systems, the directory also needs the container_file_t label. The :Z suffix on the volume flag relabels the directory automatically .
# This may fail under rootless Podman
podman run -v /host/data:/data nginx
# The fix is the :Z suffix for SELinux relabeling
podman run -v /host/data:/data:Z nginx
The podman kube generate command has a specific note about this. When generating YAML for a rootless pod on an SELinux system with volumes, the generated YAML must either add privileged: true to the pod spec, add type: spc_t under securityContext.seLinuxOptions, or the volume must be relabeled with chcon -t container_file_t -R <directory> . Without one of these, the pod will fail to access the volume when it is deployed to a Kubernetes cluster.
c. Generating Kubernetes manifests from pods
The podman kube generate command reads a pod (or one or more containers) and produces Kubernetes YAML. The default output is a Pod specification, but the --type flag can generate Deployment, DaemonSet, or Job . The --service flag adds a Kubernetes Service object with a NodePort for the pod’s port mappings .
# Create a pod with a port mapping
podman pod create --name webapp -p 8080:80
# Run the container inside the pod
podman run -d --pod webapp --name web nginx:latest
# Generate the Kubernetes YAML
podman kube generate webapp --service > webapp.yaml
# The YAML contains the Pod and the Service
# Apply it to a cluster
kubectl apply -f webapp.yaml
The generated YAML has three sections that matter. The Pod spec contains the container definitions, the port mappings, and the volume mounts. The Service spec, if --service is used, contains the NodePort that exposes the pod’s ports. The volume definitions depend on the type: a bind mount becomes a hostPath volume, and a named volume becomes a persistentVolumeClaim reference .
The generated YAML can be replayed locally with podman kube play. This is the round-trip: generate the manifest, deploy it to a cluster, and if the cluster deployment fails, replay the same manifest locally to reproduce the failure . The manifest is the contract between the local development environment and the production cluster, and it is the same file in both places.
Complete Example Session
The following session creates a rootless pod with a web server and a Redis cache, generates the Kubernetes manifest, and deploys it to a local Kind cluster.
# ============================================
# PART 1: CREATE THE ROOTLESS POD
# ============================================
# Create a pod with a port mapping
podman pod create --name webapp -p 8080:80
# Run the web container inside the pod
# The container shares the pod's network namespace
podman run -d --pod webapp --name web nginx:latest
# Run the Redis container inside the same pod
# It communicates with the web container over localhost
podman run -d --pod webapp --name cache redis:alpine
# Verify the pod and its containers
podman pod ps
podman ps --pod
# ============================================
# PART 2: GENERATE THE KUBERNETES MANIFEST
# ============================================
# Generate the YAML with a Service object
podman kube generate webapp --service > webapp.yaml
# Inspect the generated YAML
cat webapp.yaml
# The Pod spec contains the web and cache containers
# The Service spec exposes port 8080 as a NodePort
# ============================================
# PART 3: DEPLOY TO A KIND CLUSTER
# ============================================
# Create a local Kind cluster
kind create cluster --name podman-demo
# Apply the generated manifest
kubectl apply -f webapp.yaml --context kind-podman-demo
# Verify the pod is running
kubectl get pods --context kind-podman-demo
# ============================================
# PART 4: REPLAY LOCALLY
# ============================================
# The same manifest can be replayed with Podman
podman kube play webapp.yaml
# This is useful for debugging: if the cluster deployment fails,
# replay the manifest locally to reproduce the failure
# ============================================
# PART 5: CLEAN UP
# ============================================
podman pod rm -f webapp
kind delete cluster --name podman-demo
The five parts cover the rootless pod creation, the manifest generation, the Kind cluster deployment, the local replay, and the cleanup. What the session demonstrates is that the pod is the same abstraction in both environments, and the generated manifest is the contract between them.
Quick Reference
Podman Desktop Features
| Feature | Purpose |
|---|---|
| Dashboard | Containers, images, pods, volumes |
| Docker compatibility | Run Dockerfiles and Compose |
| Kubernetes integration | Kind, Minikube, OpenShift |
| Extensions | AI Lab, bootable containers, RHEL VMs |
Rootless User Namespace Modes
| Mode | Host User | Container User |
|---|---|---|
"" (default) | $UID | root (0) |
keep-id | $UID | $UID |
auto | $UID | unmapped |
Manifest Generation Commands
| Command | Purpose |
|---|---|
podman kube generate <pod> | Generate Pod YAML |
podman kube generate <pod> --service | Add Service object |
podman kube generate <pod> --type deployment | Generate Deployment |
podman kube play <file> | Replay locally |
Volume Mapping
| Podman Volume | Kubernetes Volume |
|---|---|
| Bind mount | hostPath |
| Named volume | persistentVolumeClaim |
SELinux Volume Note
| Option | When to Use |
|---|---|
:Z suffix | Relabel the volume for the container |
privileged: true | Generated YAML with rootless volumes |
type: spc_t | Generated YAML with rootless volumes |
Best Practices
✅ Do This:
# Use rootless Podman by default
podman pod create --name myapp -p 8080:80
# Add :Z for SELinux volume mounts
podman run -v /data:/data:Z nginx
# Generate the manifest with the service
podman kube generate myapp --service > myapp.yaml
# Test the manifest locally before deploying
podman kube play myapp.yaml
❌ Don’t Do This:
# Assume rootless volume mounts work without relabeling
podman run -v /data:/data nginx # may fail
# Generate a manifest without the SELinux fix
# and deploy it to a cluster (the pod will fail to access volumes)
# Use Docker Desktop and Podman Desktop simultaneously
# without checking which engine the CLI is pointing to
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Volume permission denied | Rootless UID mapping | :Z suffix or fix ownership |
| Generated pod cannot access volume | SELinux label missing | Add privileged or spc_t to the spec |
| Service port not exposed | --service flag not used | Regenerate with --service |
Pod not found by kube generate | Pod name typo or stopped | Verify with podman pod ps |
Real-World Examples
1. Create a rootless pod
podman pod create --name myapp -p 8080:80
2. Run a container in the pod
podman run -d --pod myapp --name web nginx:latest
3. Generate the manifest
podman kube generate myapp > myapp.yaml
4. Generate with service
podman kube generate myapp --service > myapp.yaml
5. Deploy to Kind
kubectl apply -f myapp.yaml --context kind-cluster
6. Replay locally
podman kube play myapp.yaml
7. Rootless volume with SELinux
podman run -v /data:/data:Z nginx
8. Keep UID mapping
podman run --userns=keep-id -v /data:/data nginx
9. Generate Deployment
podman kube generate myapp --type deployment > myapp.yaml
10. Podman Desktop install on RHEL
sudo dnf install podman-desktop
Visual
Podman Desktop Workflow
┌──────────────────────────────────────────────┐
│ PODMAN DESKTOP │
│ ├── Dashboard (containers, images, pods) │
│ ├── Docker compatibility │
│ ├── Kubernetes integration (Kind, Minikube) │
│ └── Extensions (AI Lab, bootable containers)│
└──────────────────────────────────────────────┘
Rootless Pod
┌──────────────────────────────────────────────┐
│ HOST (user UID 1000) │
│ │ │
│ ▼ │
│ POD (infra container) │
│ ├── web container (root → UID 1000) │
│ └── cache container (root → UID 1000) │
│ │
│ Shared network namespace │
│ Port 8080 mapped to host │
└──────────────────────────────────────────────┘
Manifest Generation
┌──────────────────────────────────────────────┐
│ podman kube generate myapp │
│ │ │
│ ▼ │
│ myapp.yaml │
│ ├── Pod spec (containers, ports, volumes) │
│ └── Service spec (NodePort) │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ ▼ ▼ │
│ kubectl podman │
│ apply kube play │
└──────────────────────────────────────────────┘
Volume Mapping
┌──────────────────────────────────────────────┐
│ PODMAN VOLUME │
│ ├── Bind mount → hostPath │
│ └── Named volume → persistentVolumeClaim │
│ │
│ ROOTLESS + SELINUX: │
│ Add :Z to the volume flag │
│ Or add privileged/spc_t to the generated spec│
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Podman Desktop | GUI for Podman, Kubernetes, extensions |
| Pod | Shared network namespace, infra container |
| Rootless default | User UID mapped to root in container |
--userns=keep-id | Map host UID to same UID in container |
| Manifest generation | podman kube generate |
| Service generation | --service flag |
| Volume mapping | hostPath / persistentVolumeClaim |
| SELinux fix | :Z suffix or spc_t in spec |
| Local replay | podman kube play |
Key takeaways:
- Podman Desktop is the GUI, Podman is the engine. Podman Desktop installs and configures Podman, provides a dashboard, and hosts extensions for Kubernetes, AI, and bootable containers .
- Rootless pods are the local equivalent of Kubernetes pods. The pod groups containers under a shared network namespace, and the rootless user namespace maps the host UID to the container’s root, so the pod never has host root privileges .
podman kube generateproduces the Kubernetes manifest from the running pod. The generated YAML can be deployed to a cluster withkubectl applyor replayed locally withpodman kube play.- The volume mapping is the SELinux friction point. A bind mount becomes a
hostPath, a named volume becomes apersistentVolumeClaim, and a rootless pod with volumes on an SELinux system needs the:Zsuffix or a spec modification to access the volume . - The
--serviceflag adds the Kubernetes Service. Without it, the generated YAML contains only the Pod, and the port mapping is not exposed through a Service . - The workflow is local to cluster to local. Create the pod, generate the manifest, deploy to a cluster, and if the deployment fails, replay the manifest locally to reproduce the failure.
Remember: Podman Desktop, rootless pods, and podman kube generate form a single workflow. The Desktop provides the interface, the rootless pod provides the security model and the topology that matches Kubernetes, and the manifest generation provides the bridge between the local machine and the cluster. The pod is the same abstraction in both places, and the generated YAML is the contract that makes the deployment reproducible. The friction points are the volume mounts and the SELinux labels, and the fix is a :Z suffix or a spec modification, not a change of approach.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!