Covers images, containers, Containerfile, build, volumes, networks, pods, podman-compose, systemd integration, and Quadlet files.
404 lines
9.8 KiB
Markdown
404 lines
9.8 KiB
Markdown
# 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 <image-id> # 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)
|