docs: add podman howto
Covers images, containers, Containerfile, build, volumes, networks, pods, podman-compose, systemd integration, and Quadlet files.
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -4,7 +4,7 @@
|
||||
|
||||
- [x] git — config, daily workflow, stash, remotes, branching, rebase, bisect, recovery
|
||||
- [x] ansible — playbook patterns, inventory, vault, variables, roles
|
||||
- [ ] podman — build, run, compose, volumes
|
||||
- [x] podman — images, containers, build, volumes, networks, pods, compose, systemd, quadlet
|
||||
- [x] jq — filters, select, map, slurp, recipes, @format
|
||||
- [x] curl — methods, headers, auth, upload, download, cookies, -w format
|
||||
- [x] systemd — units, journalctl, timers, targets, dependencies, hardening
|
||||
|
||||
403
topics/podman.md
Normal file
403
topics/podman.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user