Docker Installation¶
Docker is the easiest way to run BamDude. One command and you're done.
Quick Start¶
Open http://localhost:8000 in your browser.
Configuration¶
docker-compose.yml (host mode — Linux, recommended)¶
services:
bamdude:
image: ghcr.io/kainpl/bamdude:latest
build: .
container_name: bamdude
# Drop volume permission warnings: match the host user that owns
# /var/lib/docker/volumes — usually 1000:1000 on Debian / Ubuntu.
# Discover with: id -u && id -g
user: "${PUID:-1000}:${PGID:-1000}"
# Allow binding to privileged ports (322 RTSP, 990 FTPS) as non-root.
cap_add:
- NET_BIND_SERVICE
# Linux only — Docker Desktop on macOS / Windows doesn't support host mode.
# On those, comment this out and use the bridge-mode block below instead.
network_mode: host
volumes:
- bamdude_data:/app/data
- bamdude_logs:/app/logs
# Share virtual-printer certs with a parallel native install if you have one.
- ./virtual_printer:/app/data/virtual_printer
environment:
- TZ=${TZ:-Europe/Kyiv}
- PORT=${PORT:-8000}
restart: unless-stopped
volumes:
bamdude_data:
bamdude_logs:
docker-compose.yml (bridge mode — macOS / Windows / strict networking)¶
Docker Desktop on macOS / Windows doesn't support network_mode: host, and some hardened Linux setups prefer not to use it either. With bridge mode you have to map every port the printer talks to and every port the virtual printer listens on. Auto-discovery of physical printers stops working — add them manually by IP from the UI.
services:
bamdude:
image: ghcr.io/kainpl/bamdude:latest
container_name: bamdude
user: "${PUID:-1000}:${PGID:-1000}"
cap_add:
- NET_BIND_SERVICE
ports:
- "${PORT:-8000}:8000" # Web UI + REST + WebSocket
- "322:322" # Virtual-printer RTSP camera proxy
- "990:990" # Virtual-printer FTPS control
- "3000:3000" # Virtual-printer bind/detect
- "3002:3002" # Virtual-printer bind/detect alt
- "6000:6000" # Virtual-printer file tunnel
- "8883:8883" # Virtual-printer MQTT
- "2024-2026:2024-2026" # Virtual-printer A1 / P1S range
- "50000-50100:50000-50100" # Virtual-printer FTP PASV data
volumes:
- bamdude_data:/app/data
- bamdude_logs:/app/logs
environment:
- TZ=${TZ:-Europe/Kyiv}
- PORT=${PORT:-8000}
# Required for FTP PASV to work behind NAT — set to the Docker host's
# LAN IP. The slicer needs this to open the data connection.
- VIRTUAL_PRINTER_PASV_ADDRESS=${VIRTUAL_PRINTER_PASV_ADDRESS:-}
restart: unless-stopped
volumes:
bamdude_data:
bamdude_logs:
Environment Variables¶
| Variable | Default | Description |
|---|---|---|
TZ |
UTC |
Your timezone (e.g., America/New_York) |
PORT |
8000 |
Port BamDude runs on |
DEBUG |
false |
Enable debug logging |
LOG_LEVEL |
INFO |
Log level: DEBUG, INFO, WARNING, ERROR |
LOG_TO_FILE |
true |
Write logs to /app/logs/bamdude.log |
DATABASE_URL |
unset (SQLite) | A URL such as postgresql+asyncpg://user:pass@host:5432/bamdude uses your own server; for a separate PostgreSQL container use the shipped docker-compose.postgres.yml override — mind the host-networking note in PostgreSQL Support. embedded (the PostgreSQL bundled with BamDude) is not available in Docker: the image runs as root and initdb refuses to run as root. |
EMBEDDED_PG_PORT |
picked once, remembered | Pin the bundled server's port (e.g. 6432). |
TRUSTED_PROXY_IPS |
empty | Comma-separated reverse-proxy IPs trusted for X-Forwarded-For (set this when fronting BamDude with nginx / Caddy / Traefik) |
AUTH_REFRESH_COOKIE_SECURE |
unset (auto) | Force the refresh-cookie Secure flag. Auto-detect from request scheme by default. |
MFA_ENCRYPTION_KEY |
unset | URL-safe base64 Fernet key for at-rest encryption of TOTP / OIDC secrets. |
APP_URL |
http://localhost:5173 |
Public-facing base URL — used in password-reset / MFA emails, OIDC callbacks, and the Obico cached-frame URL. The external_url setting in Settings → System overrides this. |
PUID / PGID |
1000 / 1000 |
UID / GID the container runs as. Match the owner of your mounted volumes to avoid permission errors. |
VIRTUAL_PRINTER_PASV_ADDRESS |
unset | Override the FTP-PASV IP advertised by the virtual printer. Required in bridge mode (set to the Docker host's LAN IP); leave unset in host-mode. |
JWT_SECRET_KEY |
auto-generated, persisted | Don't change on a running install -- it invalidates all issued tokens. |
USE_SYSTEM_TRUST_STORE |
unset (off) | Opt-in. Set to any non-empty value (e.g. true) to make the container trust self-signed certificates mounted into /usr/local/share/ca-certificates. See Trusting a self-signed certificate below. |
See Installation > Environment Variables for the full list including optional integrations.
Trusting a self-signed certificate¶
Some integrations live on HTTPS endpoints with self-signed certificates — most commonly a local Home Assistant instance, but the same applies to OIDC providers or any HTTPS client BamDude talks to. Rather than disabling TLS verification (which would weaken every connection), BamDude can add your own certificate(s) to the container's trust store.
- Mount the host directory holding your
.crtfile(s) into/usr/local/share/ca-certificates - Set
USE_SYSTEM_TRUST_STORE=true
On container start the entrypoint runs update-ca-certificates --fresh and exports SSL_CERT_DIR=/etc/ssl/certs, so the whole Python stack (Home Assistant integration, OIDC, any HTTPS client) trusts the certificate from then on.
services:
bamdude:
image: ghcr.io/kainpl/bamdude:latest
container_name: bamdude
network_mode: host
volumes:
- bamdude_data:/app/data
- bamdude_logs:/app/logs
# Drop your self-signed .crt file(s) into this host directory.
- /path/to/certs:/usr/local/share/ca-certificates
environment:
- TZ=${TZ:-Europe/Kyiv}
- USE_SYSTEM_TRUST_STORE=true
restart: unless-stopped
volumes:
bamdude_data:
bamdude_logs:
Fails loudly when misconfigured
The flag is off by default. When you set it but mount no .crt files, the container exits with an error rather than starting silently — an empty mount would otherwise look like the flag did nothing. It also requires a root container: if you've set a non-root user: / PUID/PGID, the entrypoint can't write to the system trust store and exits with an error. Run the trust-store update as root (the default) — the entrypoint still drops privileges afterwards for the app itself.
Data Persistence¶
The shipped compose file mounts two named volumes plus a host-bound subdir:
| Mount | Type | Holds |
|---|---|---|
bamdude_data:/app/data |
named volume | bamdude.db (SQLite), archive/ (3MFs + thumbnails), library/ (file manager), certs/ (per-VP TLS material), uploads, backups |
bamdude_logs:/app/logs |
named volume | bamdude.log rotated app logs |
./virtual_printer:/app/data/virtual_printer |
bind-mount | Per-VP slicer certificates (shared with a parallel native install if you have one) |
Docker Compose v2 prefixes named volumes with the project name (the basename of the directory the compose file lives in), so the actual volume on disk is e.g. bamdude_bamdude_data, not bamdude_data. List all of them with docker volume ls.
Backup
To back up your data, copy the volume contents (or use the built-in Backup & Restore feature in Settings → Backup, which packages everything into a single zip). The application-level backup is preferred — it captures encryption-key metadata and scheduled-backup state that a raw tar of the volume leaves behind.
Renaming the compose folder = new empty volumes
If you upgrade by renaming ~/bamdude to ~/bamdude-old and unpacking a fresh checkout in its place, Docker Compose creates a fresh, empty bamdude_bamdude_data and your real data sits in the old bamdude-old_bamdude_data volume. This is the most common cause of "new container started empty after upgrade". See Upgrading & Migration → Data persistence for every scenario and its fix (named-volume namespacing, bind-mount path drift, container-layer-only data, PUID/PGID mismatch, accidental down -v, GUI-manager namespacing).
Updating¶
Advanced Setups¶
Reverse Proxy (Nginx)¶
server {
listen 443 ssl http2;
server_name bamdude.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
}
WebSocket Support
Make sure your reverse proxy supports WebSocket connections -- required for real-time printer updates.
Network Mode Host¶
Host network mode is required for printer discovery and camera streaming on Linux:
macOS / Windows
Docker Desktop on macOS and Windows requires port mapping instead of host mode. Copy the bridge-mode compose block above — mapping just ports: ["8000:8000"] is enough for the web UI but breaks printer discovery, the virtual printer, and FTP archive downloads. Add physical printers manually by IP from the UI.
DEBUG=true on first boot of a big install
Setting DEBUG=true causes BamDude to re-run the latest migration on every boot. With several thousand archives that means walking every 3MF on disk before the API comes up — startup goes from seconds to minutes. Switch DEBUG off after the migration cycle settles.
Signal notifications sidecar¶
The Signal CLI API notification provider talks to a signal-cli-rest-api server. If you do not run one already, the shipped docker-compose.signal.yml override adds it next to BamDude; docker-install.sh offers it as a question (or --signal). By hand, list the override in .env:
(With the PostgreSQL sidecar too, list all three files.) Then docker compose up -d. Two things remain yours to do:
- Link a number. Open
http://127.0.0.1:8081/v1/qrcodelink?device_name=BamDudein a browser on the server (from another machine:ssh -L 8081:127.0.0.1:8081 user@server, then the same URL on your laptop) and scan the QR code in Signal → Settings → Linked devices. Registering a brand-new number is possible too, but needs SMS/voice verification and usually a captcha — see Notifications. - Create the provider under Settings → Notifications → Add → Signal CLI API, with the linked number as the sender and the URL for your platform:
| Platform | Signal API URL |
|---|---|
| Linux, host networking (the default) | http://127.0.0.1:8081 |
| Docker Desktop, bridge networking | http://signal-api:8080 |
Loopback only
signal-cli-rest-api has no authentication of its own — whoever reaches the port can send from your number. The override publishes it on 127.0.0.1 only; do not change that to 0.0.0.0. The loopback publish is kept on Docker Desktop as well, because your browser needs it for the QR page.
The account's keys live in the bamdude_signal volume and are not part of BamDude's backup — lose the volume and you link again. The sidecar runs signal-cli in its json-rpc mode (one long-lived daemon, about 160 MB resident before an account is linked) and is pinned to a release rather than latest, because a signal-cli upgrade can require re-linking.
Troubleshooting¶
Container Won't Start¶
Can't Connect to Printer¶
If using bridge network mode, try network_mode: host.
Next Steps¶
Originally based on Bambuddy documentation.