Docker Internals¶
Multi-Stage Dockerfile (devops/Dockerfile)¶
Builder Stage¶
Based on python:3.11-slim, this stage compiles everything:
- Python venv - installs all dependencies (
gunicorn,flask,flask-babel,apscheduler,cryptography,argon2-cffi) into/opt/venv - Babel compilation -
pybabel compile -d web/translationscompiles.po→.mocatalogs - esbuild - downloads architecture-aware binary (
linux-x64orlinux-arm64), bundles and minifies:web/static/js/app.js→web/static/dist/app.min.js(ESM format)web/static/css/bundle.css→web/static/dist/bundle.min.css
Production Stage¶
Slim runtime image with only what's needed:
- Installs
gosu,passwd,curl(for health checks) - Creates non-root
lastfmuser/group - Copies compiled venv, source, minified assets, and translations from builder
- Sets
PYTHONUNBUFFERED=1,PYTHONDONTWRITEBYTECODE=1 - Entrypoint:
docker-entrypoint.sh - Default CMD:
gunicorn --config gunicorn.conf.py web.app:app - Exposes port 2002
Entrypoint (devops/docker-entrypoint.sh)¶
The entrypoint handles permission mapping and file initialization:
- UID/GID matching - reads the owner UID/GID of the
/app/configmount and remaps thelastfmuser to match viausermod/groupmod. This prevents permission conflicts with host-mounted volumes. - File fixup - creates
config/search_overrides.jsonwith empty structure if missing, fixes.envif it is accidentally a directory, ensures correct ownership oncache/andconfig/ - Privilege drop - all subsequent commands run as the
lastfmuser viagosu
Gunicorn Configuration (devops/gunicorn.conf.py)¶
| Setting | Value | Notes |
|---|---|---|
| Workers | 1 |
Always 1 - sync_state lives in process memory |
| Worker class | gthread |
Threads provide concurrency |
| Threads | Auto-detected | 2 on low-resource hosts (≤1 CPU or <1 GB RAM), otherwise min(cpu, 4) + 2 (3-6) |
| Preload | Auto-detected | Disabled on low-resource hosts |
| Timeout | 120 |
Configurable via GUNICORN_TIMEOUT |
| Max requests | 1000 (+50 jitter) |
Automatic worker recycling to prevent memory leaks |
Configurable via environment variables: GUNICORN_BIND (default 0.0.0.0:2002), GUNICORN_THREADS, GUNICORN_TIMEOUT, GUNICORN_LOG_LEVEL, GUNICORN_PRELOAD.
After forking, post_fork() initializes the APScheduler instance from env settings.
Docker Compose (devops/docker-compose.yml)¶
| Feature | Configuration |
|---|---|
| Port | ${YTMT_PORT:-2002}:2002 (configurable via env) |
| Health check | curl -f http://localhost:2002/ every 30s, 10s timeout, 3 retries, 10s start period |
| Memory | 512 MB limit, 128 MB reservation |
| Restart | unless-stopped |
| Timezone | Host /etc/localtime mounted read-only + TZ env fallback |
Volume mounts:
| Host path | Container path | Mode |
|---|---|---|
cache/ |
/app/cache |
read-write |
config/ |
/app/config |
read-write |
browser.json |
/app/browser.json |
read-write |
.env |
/app/.env |
read-write |
.env.example |
/app/.env.example |
read-only |
/etc/localtime |
/etc/localtime |
read-only |