# Podman > Daemonless container engine — Docker-compatible, rootless by default. ## Core Concepts | Concept | Description | |-------------|----------------------------------------------------| | Image | Read-only template for creating containers | | Container | Running (or stopped) instance of an image | | Pod | Group of containers sharing network/IPC namespace | | Volume | Persistent storage decoupled from container life | | Registry | Remote image store (docker.io, ghcr.io, quay.io) | | Containerfile | Build instructions (alias: Dockerfile) | ## Images ```bash # Search podman search nginx podman search --list-tags docker.io/library/python # Pull podman pull nginx podman pull docker.io/library/python:3.12-slim # List podman images podman images --format "{{.Repository}}:{{.Tag}} {{.Size}}" # Inspect podman inspect nginx:latest podman image inspect nginx:latest --format '{{.Config.ExposedPorts}}' # Remove podman rmi nginx:latest podman rmi -f # force podman image prune # remove dangling podman image prune -a # remove all unused ``` ## Containers ```bash # Run podman run nginx # foreground podman run -d nginx # detached podman run --rm nginx cat /etc/os-release # run + remove after exit podman run -it ubuntu bash # interactive terminal # Common run flags podman run -d \ --name myapp \ -p 8080:80 \ # host:container port -v ./data:/app/data:Z \ # bind mount (:Z for SELinux) -e APP_ENV=production \ # environment variable --env-file .env \ # env from file -w /app \ # working directory --restart unless-stopped \ # restart policy --memory 512m \ # memory limit --cpus 1.5 \ # CPU limit myimage:latest # List podman ps # running podman ps -a # all (including stopped) podman ps --format "{{.Names}} {{.Status}} {{.Ports}}" # Lifecycle podman start myapp podman stop myapp podman stop -t 5 myapp # 5s grace period podman restart myapp podman kill myapp # SIGKILL podman rm myapp podman rm -f myapp # force (stop + remove) # Exec into running container podman exec -it myapp bash podman exec myapp cat /etc/hostname # Logs podman logs myapp podman logs -f myapp # follow podman logs --tail 50 myapp # last 50 lines podman logs --since 1h myapp # last hour # Copy files podman cp myapp:/app/log.txt ./log.txt podman cp ./config.yml myapp:/app/config.yml # Stats podman stats # all containers podman stats myapp # specific # Inspect podman inspect myapp podman inspect myapp --format '{{.NetworkSettings.IPAddress}}' # Cleanup podman container prune # remove all stopped ``` ### Port Mapping ```bash -p 8080:80 # host 8080 -> container 80 -p 127.0.0.1:8080:80 # bind to localhost only -p 8080:80/udp # UDP -p 8080-8090:80-90 # port range --network host # use host network directly ``` ### Restart Policies | Policy | Behavior | |--------------------|---------------------------------------------| | `no` | Never restart (default) | | `on-failure[:N]` | Restart on non-zero exit [max N times] | | `always` | Always restart, including on boot | | `unless-stopped` | Like always, but not if manually stopped | ## Building Images ### Containerfile ```dockerfile FROM python:3.12-slim ARG APP_VERSION=1.0.0 ENV APP_ENV=production WORKDIR /app # Dependencies first (layer caching) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Application code COPY src/ ./src/ EXPOSE 8080 RUN useradd -r appuser USER appuser ENTRYPOINT ["python", "-m", "src.app"] CMD ["--port", "8080"] ``` ### Build Commands ```bash # Build podman build -t myapp . podman build -t myapp:v1.2 -f Containerfile . # Build args podman build --build-arg APP_VERSION=2.0 -t myapp . # No cache podman build --no-cache -t myapp . # Multi-stage (use target) podman build --target builder -t myapp-build . # Build from stdin cat Containerfile | podman build -t myapp - # Tag podman tag myapp:latest myapp:v1.2 podman tag myapp registry.example.com/myapp:v1.2 # Push to registry podman login registry.example.com podman push registry.example.com/myapp:v1.2 # Save / load (offline transfer) podman save myapp:latest -o myapp.tar podman load -i myapp.tar # History (show layers) podman history myapp:latest ``` ### Containerfile Best Practices | Practice | Reason | |----------|--------| | Pin base image versions | Reproducible builds | | `COPY` deps before code | Maximize layer cache | | `--no-cache-dir` on pip | Smaller image | | Use `-slim` base images | Smaller attack surface | | `USER nonroot` | Don't run as root | | One `RUN` per logical step | Readable + cacheable | | `.containerignore` | Exclude `.git`, `node_modules`, etc. | ## Volumes ```bash # Create named volume podman volume create mydata # List / inspect podman volume ls podman volume inspect mydata # Use in container podman run -d -v mydata:/app/data myapp # Bind mount (host directory) podman run -d -v /host/path:/container/path:Z myapp podman run -d -v ./local:/app/data:Z myapp # relative path # Read-only mount podman run -d -v ./config:/app/config:ro,Z myapp # Tmpfs (in-memory) podman run -d --tmpfs /tmp:size=100m myapp # Remove podman volume rm mydata podman volume prune # remove unused ``` ### Mount Flags | Flag | Purpose | |-------|--------------------------------------------------| | `:Z` | Private SELinux label (single container) | | `:z` | Shared SELinux label (multiple containers) | | `:ro` | Read-only | | `:U` | Chown to container user (rootless permission fix)| ## Networks ```bash # List podman network ls # Create podman network create mynet podman network create --subnet 10.89.0.0/24 mynet # Run on network podman run -d --network mynet --name web myapp podman run -d --network mynet --name db postgres # Containers on same network resolve by name # web can reach db at hostname "db" # Connect/disconnect running container podman network connect mynet myapp podman network disconnect mynet myapp # Inspect podman network inspect mynet # Remove podman network rm mynet podman network prune ``` ## Pods ```bash # Create pod (shared network namespace) podman pod create --name mypod -p 8080:80 # Run containers in pod podman run -d --pod mypod --name web nginx podman run -d --pod mypod --name app myapp # Containers in same pod share localhost # Pod management podman pod ls podman pod start mypod podman pod stop mypod podman pod rm mypod podman pod rm -f mypod # force # Generate kube YAML from pod podman generate kube mypod > pod.yml # Create pod from kube YAML podman play kube pod.yml podman play kube --down pod.yml # tear down ``` ## Podman Compose ```yaml # compose.yml services: web: build: . ports: - "8080:80" volumes: - ./src:/app/src:Z environment: - APP_ENV=development depends_on: - db restart: unless-stopped db: image: postgres:16 volumes: - pgdata:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: secret POSTGRES_DB: myapp volumes: pgdata: ``` ```bash podman-compose up -d podman-compose down podman-compose logs -f web podman-compose ps podman-compose exec web bash podman-compose build podman-compose pull ``` ## Systemd Integration ```bash # Generate systemd unit from container podman generate systemd --new --name myapp > ~/.config/systemd/user/myapp.service # For rootless, enable lingering loginctl enable-linger $USER # Enable as user service systemctl --user daemon-reload systemctl --user enable --now myapp.service # Quadlet (modern, preferred over generate systemd) # Place .container file in ~/.config/containers/systemd/ ``` ### Quadlet File ```ini # ~/.config/containers/systemd/myapp.container [Container] Image=myapp:latest PublishPort=8080:80 Volume=./data:/app/data:Z Environment=APP_ENV=production [Service] Restart=on-failure [Install] WantedBy=default.target ``` ```bash systemctl --user daemon-reload systemctl --user start myapp.service ``` ## Cleanup ```bash # Remove stopped containers podman container prune # Remove unused images podman image prune -a # Remove unused volumes podman volume prune # Remove unused networks podman network prune # Nuclear option — everything unused podman system prune -a --volumes ``` ## Gotchas - `:Z`/`:z` is required on SELinux systems (Fedora, RHEL) or mounts fail silently - Rootless containers can't bind to ports < 1024 — use higher ports or `sysctl net.ipv4.ip_unprivileged_port_start` - `podman-compose` is a separate package — not bundled with podman - Rootless volume mounts may have UID mapping issues — use `:U` flag or `--userns=keep-id` - `podman generate systemd` is deprecated — use Quadlet `.container` files instead - Container DNS resolution by name only works on user-created networks, not the default `podman` network - `podman system prune` does not remove named volumes unless `--volumes` is passed - `--restart` policies only work with `podman start`, not across reboots — use systemd for boot persistence ## See Also - `systemd` — service management for container persistence - [Podman docs](https://docs.podman.io/) - [Quadlet docs](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)