docs: add podman-compose and podman-systemd howtos
- podman-compose.md — full compose.yml reference, services, healthchecks, dependencies, networking, volumes, override files - podman-systemd.md — Quadlet files (.container, .volume, .network, .kube, .build), multi-container stacks, lingering, auto-update, secrets
This commit is contained in:
372
topics/podman-compose.md
Normal file
372
topics/podman-compose.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# Podman Compose
|
||||
|
||||
> Multi-container orchestration with Compose files — podman's docker-compose equivalent.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# Install
|
||||
sudo apt install podman-compose # Debian/Ubuntu
|
||||
sudo dnf install podman-compose # Fedora/RHEL
|
||||
pip install podman-compose # pip fallback
|
||||
|
||||
# Verify
|
||||
podman-compose version
|
||||
```
|
||||
|
||||
## Compose File Reference
|
||||
|
||||
```yaml
|
||||
# compose.yml (or docker-compose.yml — both recognized)
|
||||
services:
|
||||
web:
|
||||
image: nginx:alpine
|
||||
container_name: web
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- ./html:/usr/share/nginx/html:ro,Z
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro,Z
|
||||
networks:
|
||||
- frontend
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: Europe/Brussels
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Containerfile
|
||||
args:
|
||||
APP_VERSION: "2.1"
|
||||
container_name: app
|
||||
expose:
|
||||
- "8000" # internal only (not published)
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./src:/app/src:Z
|
||||
- app-data:/app/data
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
APP_ENV: production
|
||||
DB_HOST: db
|
||||
DB_NAME: myapp
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
restart: on-failure
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: "1.0"
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: db
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro,Z
|
||||
environment:
|
||||
POSTGRES_USER: appuser
|
||||
POSTGRES_PASSWORD_FILE: /run/secrets/db_pass
|
||||
POSTGRES_DB: myapp
|
||||
secrets:
|
||||
- db_pass
|
||||
networks:
|
||||
- backend
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:5432:5432" # localhost only
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: redis
|
||||
command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
networks:
|
||||
- backend
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
redis-data:
|
||||
app-data:
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
backend:
|
||||
|
||||
secrets:
|
||||
db_pass:
|
||||
file: ./secrets/db_password.txt
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Start / stop
|
||||
podman-compose up -d # detached
|
||||
podman-compose up -d --build # rebuild before start
|
||||
podman-compose down # stop + remove containers
|
||||
podman-compose down -v # also remove volumes
|
||||
|
||||
# Specific services
|
||||
podman-compose up -d web app # start only these
|
||||
podman-compose stop db # stop one service
|
||||
podman-compose restart app
|
||||
|
||||
# Build
|
||||
podman-compose build # all services with build:
|
||||
podman-compose build app # specific service
|
||||
podman-compose build --no-cache app # force rebuild
|
||||
|
||||
# Logs
|
||||
podman-compose logs # all services
|
||||
podman-compose logs -f app # follow one service
|
||||
podman-compose logs --tail 100 app db # last 100 lines, multiple
|
||||
|
||||
# Status
|
||||
podman-compose ps
|
||||
podman-compose top # processes in each container
|
||||
|
||||
# Execute
|
||||
podman-compose exec app bash
|
||||
podman-compose exec db psql -U appuser -d myapp
|
||||
podman-compose exec -T app python manage.py migrate # no TTY (scripts)
|
||||
|
||||
# Run one-off command (new container)
|
||||
podman-compose run --rm app python manage.py shell
|
||||
|
||||
# Pull latest images
|
||||
podman-compose pull
|
||||
```
|
||||
|
||||
## Service Configuration
|
||||
|
||||
### Build Options
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ./app # build context directory
|
||||
dockerfile: Containerfile # relative to context
|
||||
args:
|
||||
APP_VERSION: "2.0"
|
||||
BUILD_ENV: production
|
||||
target: production # multi-stage target
|
||||
cache_from:
|
||||
- myapp:latest
|
||||
image: myapp:latest # tag the built image
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
# Inline key-value
|
||||
environment:
|
||||
APP_ENV: production
|
||||
DEBUG: "false"
|
||||
|
||||
# From file
|
||||
env_file:
|
||||
- .env # default
|
||||
- .env.production # override
|
||||
|
||||
# Pass host variable through (no value = inherit)
|
||||
environment:
|
||||
- HOME
|
||||
```
|
||||
|
||||
`.env` file (auto-loaded for variable substitution in compose.yml):
|
||||
|
||||
```bash
|
||||
# .env
|
||||
APP_VERSION=2.1
|
||||
POSTGRES_PASSWORD=secret
|
||||
EXTERNAL_PORT=8080
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Variable substitution in compose.yml
|
||||
services:
|
||||
app:
|
||||
image: myapp:${APP_VERSION}
|
||||
ports:
|
||||
- "${EXTERNAL_PORT:-8080}:80" # with default
|
||||
```
|
||||
|
||||
### Healthchecks
|
||||
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s # time between checks
|
||||
timeout: 10s # max time per check
|
||||
retries: 3 # failures before unhealthy
|
||||
start_period: 30s # grace period after start
|
||||
start_interval: 5s # interval during start_period
|
||||
```
|
||||
|
||||
| Test form | Example |
|
||||
|-----------|---------|
|
||||
| `CMD` | `["CMD", "curl", "-f", "http://localhost/health"]` |
|
||||
| `CMD-SHELL` | `["CMD-SHELL", "pg_isready \|\| exit 1"]` |
|
||||
| `NONE` | Disable inherited healthcheck |
|
||||
|
||||
### Dependency Conditions
|
||||
|
||||
```yaml
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_started # default — just started
|
||||
db:
|
||||
condition: service_healthy # wait for healthcheck to pass
|
||||
migrations:
|
||||
condition: service_completed_successfully # wait for exit 0
|
||||
```
|
||||
|
||||
### Networking
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
networks:
|
||||
frontend:
|
||||
aliases:
|
||||
- api # extra DNS name on this network
|
||||
backend:
|
||||
ipv4_address: 10.89.0.10 # static IP
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
backend:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 10.89.0.0/24
|
||||
gateway: 10.89.0.1
|
||||
external-net:
|
||||
external: true # pre-existing network
|
||||
```
|
||||
|
||||
Containers on the same network resolve each other by service name.
|
||||
|
||||
### Volumes
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
volumes:
|
||||
# Bind mounts
|
||||
- ./src:/app/src:Z # relative path
|
||||
- /host/path:/container/path:ro # absolute, read-only
|
||||
|
||||
# Named volumes
|
||||
- app-data:/app/data
|
||||
|
||||
# Tmpfs
|
||||
- type: tmpfs
|
||||
target: /tmp
|
||||
tmpfs:
|
||||
size: 100000000 # 100MB
|
||||
|
||||
volumes:
|
||||
app-data: # managed by podman
|
||||
shared-data:
|
||||
external: true # pre-existing volume
|
||||
backup:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: /mnt/backup/app
|
||||
```
|
||||
|
||||
## Development Patterns
|
||||
|
||||
### Override File
|
||||
|
||||
```yaml
|
||||
# compose.override.yml — auto-merged with compose.yml
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
volumes:
|
||||
- ./src:/app/src:Z # live reload
|
||||
environment:
|
||||
APP_ENV: development
|
||||
DEBUG: "true"
|
||||
ports:
|
||||
- "5678:5678" # debugger port
|
||||
command: python -m debugpy --listen 0.0.0.0:5678 -m src.app
|
||||
```
|
||||
|
||||
```bash
|
||||
# Production (skip override)
|
||||
podman-compose -f compose.yml up -d
|
||||
|
||||
# Explicit files
|
||||
podman-compose -f compose.yml -f compose.prod.yml up -d
|
||||
```
|
||||
|
||||
### Wait-for Pattern
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
# OR with a wrapper script:
|
||||
command: ["./wait-for-it.sh", "db:5432", "--", "python", "app.py"]
|
||||
```
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `podman-compose` and `docker-compose` are mostly compatible but edge cases differ — test both if porting
|
||||
- `:Z` on volumes is required for SELinux (Fedora/RHEL) — omitting it causes silent permission errors
|
||||
- `depends_on: service_healthy` requires a `healthcheck` on the dependency — no healthcheck = hangs
|
||||
- `env_file` values are literal — no shell expansion, no quotes needed (quotes become part of value)
|
||||
- `.env` is loaded for compose variable substitution only — use `env_file` to pass vars into containers
|
||||
- Named volumes persist across `down` — only `down -v` removes them
|
||||
- `podman-compose` runs containers independently, not in a pod by default — use `--podman-run-args="--pod"` or configure per-project
|
||||
- DNS resolution between containers only works on user-created networks
|
||||
- `container_name` prevents scaling (`podman-compose up --scale app=3`)
|
||||
- Secret files are mounted read-only at `/run/secrets/<name>` inside the container
|
||||
|
||||
## See Also
|
||||
|
||||
- `podman` — core container commands
|
||||
- `podman-systemd` — Quadlet files for production deployment
|
||||
- [Compose spec](https://compose-spec.io/)
|
||||
Reference in New Issue
Block a user