New docs/DEPLOY.md covering container image, compose config, volume mounts, host networking, operations, and troubleshooting. Updated README, INSTALL, CHEATSHEET, and DEBUG to reference it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
235 lines
4.4 KiB
Markdown
235 lines
4.4 KiB
Markdown
# Podman Deployment
|
|
|
|
## Overview
|
|
|
|
The bouncer runs in a rootless podman container with host networking. Source code
|
|
and configuration are mounted as volumes -- the container image only contains
|
|
Python and pip dependencies.
|
|
|
|
```
|
|
Container (bouncer)
|
|
|- python:3.12-slim + deps
|
|
|- /app/src <- ./src (read-only volume)
|
|
|- /data <- ./config (read-write volume)
|
|
```
|
|
|
|
## Prerequisites
|
|
|
|
- `podman` and `podman-compose`
|
|
- SOCKS5 proxy reachable at `127.0.0.1:1080` (host network)
|
|
|
|
Verify:
|
|
|
|
```bash
|
|
podman --version
|
|
podman-compose --version
|
|
ss -tlnp | grep 1080
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
cd ~/git/bouncer
|
|
|
|
# create config from template
|
|
cp config/bouncer.example.toml config/bouncer.toml
|
|
$EDITOR config/bouncer.toml
|
|
|
|
# build and start
|
|
make build
|
|
make up
|
|
|
|
# check logs
|
|
make logs
|
|
```
|
|
|
|
## Container Image
|
|
|
|
The image installs only runtime dependencies. Source code is not baked in.
|
|
|
|
```bash
|
|
# build
|
|
podman build -t bouncer -f Containerfile .
|
|
|
|
# rebuild after dependency changes
|
|
podman build --no-cache -t bouncer -f Containerfile .
|
|
```
|
|
|
|
### Containerfile
|
|
|
|
```dockerfile
|
|
FROM python:3.12-slim
|
|
WORKDIR /app
|
|
RUN pip install --no-cache-dir \
|
|
"python-socks[asyncio]>=2.4" \
|
|
"aiosqlite>=0.19"
|
|
ENV PYTHONUNBUFFERED=1
|
|
ENV PYTHONPATH=/app/src
|
|
VOLUME /app/src
|
|
VOLUME /data
|
|
ENTRYPOINT ["python", "-m", "bouncer"]
|
|
CMD ["-c", "/data/bouncer.toml"]
|
|
```
|
|
|
|
## Compose
|
|
|
|
```yaml
|
|
services:
|
|
bouncer:
|
|
build:
|
|
context: .
|
|
dockerfile: Containerfile
|
|
container_name: bouncer
|
|
restart: unless-stopped
|
|
network_mode: host
|
|
logging:
|
|
driver: k8s-file
|
|
volumes:
|
|
- ./src:/app/src:Z,ro
|
|
- ./config:/data:Z
|
|
command: ["-c", "/data/bouncer.toml", "-v"]
|
|
```
|
|
|
|
### Volume Mounts
|
|
|
|
| Host Path | Container Path | Mode | Purpose |
|
|
|-----------|---------------|------|---------|
|
|
| `./src` | `/app/src` | read-only | Python source code |
|
|
| `./config` | `/data` | read-write | Config + SQLite backlog |
|
|
|
|
The `:Z` suffix applies the correct SELinux context for rootless podman.
|
|
|
|
### Host Networking
|
|
|
|
`network_mode: host` is required because the bouncer needs access to:
|
|
|
|
- `127.0.0.1:1080` -- SOCKS5 proxy (outbound IRC connections)
|
|
- `127.0.0.1:6667` -- client listen port (inbound IRC clients)
|
|
|
|
### Log Driver
|
|
|
|
`k8s-file` is used instead of the default `journald` driver to ensure
|
|
`podman logs` works reliably in rootless mode.
|
|
|
|
## Operations
|
|
|
|
### Start / Stop / Restart
|
|
|
|
```bash
|
|
podman-compose up -d # start (detached)
|
|
podman-compose down # stop and remove
|
|
podman-compose restart # restart
|
|
```
|
|
|
|
Or with make targets:
|
|
|
|
```bash
|
|
make up # podman-compose up -d
|
|
make down # podman-compose down
|
|
```
|
|
|
|
### Logs
|
|
|
|
```bash
|
|
podman logs -f bouncer # follow all logs
|
|
podman logs --tail 50 bouncer # last 50 lines
|
|
podman logs bouncer 2>&1 | grep -v aiosqlite # filter sqlite noise
|
|
podman logs bouncer 2>&1 | grep -E 'INFO|WARN' # important events only
|
|
```
|
|
|
|
Or:
|
|
|
|
```bash
|
|
make logs # podman logs -f bouncer
|
|
```
|
|
|
|
### Status
|
|
|
|
```bash
|
|
podman ps --filter name=bouncer
|
|
podman inspect bouncer --format '{{.State.Status}}'
|
|
```
|
|
|
|
### Shell Access
|
|
|
|
```bash
|
|
podman exec -it bouncer bash
|
|
podman exec bouncer python -c "import bouncer; print(bouncer.__version__)"
|
|
```
|
|
|
|
### Inspect Backlog
|
|
|
|
The SQLite database is stored in the config volume:
|
|
|
|
```bash
|
|
sqlite3 config/bouncer.db "SELECT network, COUNT(*) FROM messages GROUP BY network;"
|
|
```
|
|
|
|
## Updating
|
|
|
|
After pulling new code:
|
|
|
|
```bash
|
|
git pull
|
|
podman-compose down
|
|
podman-compose up -d
|
|
```
|
|
|
|
The image only needs rebuilding if dependencies change:
|
|
|
|
```bash
|
|
podman-compose down
|
|
make build
|
|
podman-compose up -d
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Container exits immediately
|
|
|
|
Check logs for config or proxy errors:
|
|
|
|
```bash
|
|
podman logs bouncer
|
|
```
|
|
|
|
Common causes:
|
|
|
|
- Missing `config/bouncer.toml` (not mounted)
|
|
- SOCKS5 proxy not running on host
|
|
- Invalid TOML syntax
|
|
|
|
### No log output
|
|
|
|
Verify `PYTHONUNBUFFERED=1` is set in the Containerfile and the log driver
|
|
is `k8s-file`:
|
|
|
|
```bash
|
|
podman inspect bouncer --format '{{.HostConfig.LogConfig.Type}}'
|
|
```
|
|
|
|
### Container can't reach SOCKS5 proxy
|
|
|
|
`network_mode: host` must be set. Verify the proxy is listening:
|
|
|
|
```bash
|
|
ss -tlnp | grep 1080
|
|
```
|
|
|
|
### Permission denied on volumes
|
|
|
|
The `:Z` suffix handles SELinux relabeling. If issues persist:
|
|
|
|
```bash
|
|
podman unshare ls -la config/
|
|
```
|
|
|
|
### Rebuild from scratch
|
|
|
|
```bash
|
|
podman-compose down
|
|
podman rmi bouncer
|
|
podman build --no-cache -t bouncer -f Containerfile .
|
|
podman-compose up -d
|
|
```
|