Files
hamprint/Containerfile

63 lines
2.5 KiB
Docker

# 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"]