# hamprint -- single-container image (plan.md ยง10). # # Runs Gunicorn plus the two periodic jobs (process_submissions every 30 s, # cleanup_stale every 5 min) in the same process group. No sidecars. # # Build: podman build -t hamprint:latest . # Run: podman run --rm -p 8000:8000 --env-file .env hamprint:latest # # In production the host typically mounts a volume at /app/media so uploaded # STLs survive container restarts; the database connection comes from # DATABASE_URL (Postgres in prod, SQLite if you really want). FROM docker.io/library/python:3.14-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ PIP_NO_CACHE_DIR=1 \ DJANGO_SETTINGS_MODULE=hamprint.settings.prod WORKDIR /app # System packages python:3.14-slim doesn't ship: # tini -- PID 1 for clean signal forwarding to gunicorn + the loops # libgomp1 -- numpy/numpy-stl runtime on some kernels # curl -- handy for healthchecks if compose.yaml grows one RUN apt-get update \ && apt-get install -y --no-install-recommends tini curl libgomp1 \ && rm -rf /var/lib/apt/lists/* # Python deps as their own layer so app-code edits don't invalidate the wheel # cache. psycopg[binary] is added explicitly because requirements.txt is # kept Postgres-driver-agnostic for the host-venv (SQLite) path. COPY requirements.txt . RUN pip install -r requirements.txt 'psycopg[binary]' # Application code (.dockerignore excludes .venv, .git, db.sqlite3, demo/, etc). COPY . . # Build Tailwind CSS, gather everything under STATIC_ROOT for WhiteNoise, then # drop the ~120 MB Tailwind CLI download so it doesn't ride along. The source # .css lives at assets/tailwind.source.css (per TAILWIND_CLI_SRC_CSS in # settings/base.py); only the CLI binary cache is purged. RUN python manage.py tailwind build --force \ && python manage.py collectstatic --noinput \ && rm -rf /app/.django_tailwind_cli # Default writable dirs. Mount a volume at /app/media in prod for persistence. RUN mkdir -p /app/media /app/staticfiles # Drop privileges. uid 1000 maps cleanly to the typical host user in rootless # podman, so a bind-mounted media volume stays writable without extra fuss. RUN useradd -m -u 1000 app \ && chown -R app:app /app USER app EXPOSE 8000 # tini reaps zombies + forwards SIGTERM to all children, so when the orchestrator # stops the container both Gunicorn AND the two background loops get the signal. ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["bash", "entrypoint.sh"]