chore: dockerfile (testing)
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
.git
|
||||
.gitignore
|
||||
.venv
|
||||
venv
|
||||
**/__pycache__
|
||||
**/*.pyc
|
||||
*.egg-info
|
||||
.ruff_cache
|
||||
.pytest_cache
|
||||
ctagsfile
|
||||
images
|
||||
main.py
|
||||
gifs_opus_magnum
|
||||
|
||||
polylan_submitter/node_modules
|
||||
polylan_submitter/static
|
||||
polylan_submitter/static_source/vite
|
||||
polylan_submitter/media
|
||||
polylan_submitter/db.sqlite3
|
||||
polylan_submitter/.vscode
|
||||
polylan_submitter/settingsLocal.py
|
||||
|
||||
deploy/supervisor/polylan-submitter.env
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# Single OCI image bundling the Vue/Vite frontend, the Django/Channels web
|
||||
# process (daphne) and the RQ worker, managed together by supervisord - see
|
||||
# deploy/docker/README.md for why (single-container Proxmox deploy) and how
|
||||
# to run it. Postgres, Redis and S3 stay external, configured the same way
|
||||
# as the bare-metal deploy in deploy/ (see deploy/supervisor/*.env.dist for
|
||||
# the full env var list) - this image carries no infra, just the app.
|
||||
#
|
||||
# Build (from repo root, requires BuildKit/buildx - RUN --mount=type=cache
|
||||
# below is rejected by the legacy builder):
|
||||
# docker buildx build --load -t polylan-submitter .
|
||||
|
||||
# ---- frontend build -------------------------------------------------------
|
||||
FROM node:22-alpine AS frontend-builder
|
||||
WORKDIR /app
|
||||
COPY polylan_submitter/package.json polylan_submitter/pnpm-lock.yaml ./
|
||||
RUN corepack enable && corepack prepare pnpm@9.0.0 --activate \
|
||||
&& pnpm install --frozen-lockfile
|
||||
COPY polylan_submitter/vite.config.ts polylan_submitter/tsconfig*.json ./
|
||||
COPY polylan_submitter/src ./src
|
||||
COPY polylan_submitter/public ./public
|
||||
RUN pnpm build
|
||||
|
||||
# ---- python dependencies ---------------------------------------------------
|
||||
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS python-builder
|
||||
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml uv.lock ./
|
||||
# --no-install-project: this pyproject has no [build-system]/package to
|
||||
# install, it only pins the app's dependencies - so just build the venv.
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-dev --no-install-project
|
||||
|
||||
# ---- runtime ----------------------------------------------------------------
|
||||
FROM python:3.13-slim AS runtime
|
||||
|
||||
# tesseract-ocr: shelled out to by pytesseract (submissions/tasks.py,
|
||||
# opus_magnum/utils.py) for OCR'ing Opus Magnum submission GIFs.
|
||||
# libgl1/libglib2.0-0: runtime shared libs opencv-python needs even though
|
||||
# it ships as a wheel.
|
||||
# libpq5: psycopg (plain, not psycopg[binary]/[c]) loads libpq via ctypes.
|
||||
# supervisor/tini: process supervision - see deploy/docker/supervisord.conf.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
tesseract-ocr \
|
||||
libgl1 \
|
||||
libglib2.0-0 \
|
||||
libpq5 \
|
||||
supervisor \
|
||||
tini \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd --system app && useradd --system --gid app --create-home app
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=python-builder --chown=app:app /app/.venv /app/.venv
|
||||
ENV PATH="/app/.venv/bin:$PATH" \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
DJANGO_SETTINGS_MODULE=polylan_submitter.settings
|
||||
|
||||
COPY --chown=app:app polylan_submitter/ /app/polylan_submitter/
|
||||
COPY --from=frontend-builder --chown=app:app /app/static_source/vite /app/polylan_submitter/static_source/vite
|
||||
COPY --chown=app:app deploy/docker/entrypoint.sh /app/entrypoint.sh
|
||||
COPY --chown=app:app deploy/docker/supervisord.conf /app/supervisord.conf
|
||||
|
||||
RUN rm -rf /app/polylan_submitter/node_modules /app/polylan_submitter/settingsLocal.py \
|
||||
&& mkdir -p /app/polylan_submitter/static /app/polylan_submitter/media \
|
||||
&& chown app:app /app/polylan_submitter/static /app/polylan_submitter/media \
|
||||
&& chmod +x /app/entrypoint.sh
|
||||
|
||||
USER app
|
||||
WORKDIR /app/polylan_submitter
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["tini", "--", "/app/entrypoint.sh"]
|
||||
CMD ["supervisord", "-c", "/app/supervisord.conf", "-n"]
|
||||
@@ -0,0 +1,71 @@
|
||||
# Container image
|
||||
|
||||
`Dockerfile` (repo root) builds a single OCI image with the Vite frontend
|
||||
already built in, and a `.venv` with all Python deps. At runtime it uses
|
||||
`supervisord` (`deploy/docker/supervisord.conf`) to run two processes in the
|
||||
one container:
|
||||
|
||||
- `web` - one `daphne` process serving the Django/Channels app on `:8000`
|
||||
- `worker` - two `rqworker` processes for background jobs (submission OCR,
|
||||
AAndT sync, etc.)
|
||||
|
||||
Postgres, Redis and S3-compatible storage are **not** bundled - the image
|
||||
expects them the same way the bare-metal deploy does (see
|
||||
`deploy/supervisor/polylan-submitter.env.dist` for the full list of env
|
||||
vars: `POSTGRES_*`, `REDIS_HOST`/`REDIS_PORT`, `AWS_*`, `DJANGO_SECRET_KEY`,
|
||||
etc.). Point those at wherever those services already live (other
|
||||
containers/LXCs, or existing infra).
|
||||
|
||||
## Build
|
||||
|
||||
Requires BuildKit/buildx (`RUN --mount=type=cache` in the Dockerfile is
|
||||
rejected by the legacy builder):
|
||||
|
||||
```sh
|
||||
docker buildx build --load -t polylan-submitter .
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```sh
|
||||
docker run -d \
|
||||
--name polylan-submitter \
|
||||
--env-file deploy/supervisor/polylan-submitter.env \
|
||||
-p 8000:8000 \
|
||||
polylan-submitter
|
||||
```
|
||||
|
||||
Migrations run automatically on container start (set `MIGRATE_ON_START=false`
|
||||
to skip). `collectstatic` does not run automatically - it pushes to the
|
||||
shared S3 bucket, so run it once per deploy instead of racing multiple
|
||||
replicas on every boot:
|
||||
|
||||
```sh
|
||||
docker run --rm --env-file deploy/supervisor/polylan-submitter.env \
|
||||
polylan-submitter python manage.py collectstatic --noinput
|
||||
```
|
||||
|
||||
## Scaling
|
||||
|
||||
There's one `daphne` process per container (unlike the bare-metal deploy's
|
||||
`NUM_WEB_WORKERS` behind HAProxy) - scale by running multiple container
|
||||
replicas behind an external reverse proxy/load balancer instead.
|
||||
|
||||
## Deploying to Proxmox
|
||||
|
||||
Recent Proxmox VE versions can create an LXC container directly from an OCI
|
||||
image (`pct create <vmid> <image-ref> ...` with an OCI-format image
|
||||
reference, currently a tech-preview feature - check `pct create --help` and
|
||||
the Proxmox VE docs for your installed version, since the exact flags have
|
||||
been changing across releases). Practically that means:
|
||||
|
||||
1. Push the built image to a registry Proxmox can reach (a local registry,
|
||||
GHCR, Docker Hub, etc.) - `docker tag`/`docker push`.
|
||||
2. Create the container on the Proxmox host from that image reference,
|
||||
passing the same env vars as above.
|
||||
3. Map/forward port 8000 as needed, and put whatever TLS-terminating
|
||||
reverse proxy you already run in front of it.
|
||||
|
||||
If OCI-to-LXC isn't available on your Proxmox version, the same image runs
|
||||
fine in a plain VM (or an LXC) with any container runtime (Docker/Podman)
|
||||
installed instead.
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Container entrypoint: runs migrations (unless disabled), then execs
|
||||
# whatever CMD supplied (supervisord by default - see the Dockerfile).
|
||||
#
|
||||
# collectstatic is intentionally NOT run here: it pushes to the shared S3
|
||||
# bucket (see STORAGES in settings.py), so running it on every container
|
||||
# start would race across replicas for no benefit - run it once per deploy
|
||||
# instead:
|
||||
# docker run --rm --env-file <env file> polylan-submitter \
|
||||
# python manage.py collectstatic --noinput
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${MIGRATE_ON_START:-true}" = "true" ]; then
|
||||
echo "Running migrations..."
|
||||
python manage.py migrate --noinput
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env sh
|
||||
# One-off build+push to the Gitea container registry for testing.
|
||||
# Usage: deploy/docker/push.sh [tag] (run from repo root, default tag: test)
|
||||
set -eu
|
||||
|
||||
REGISTRY=git.legrems.mom
|
||||
OWNER=polylan
|
||||
IMAGE=polylan-submitter
|
||||
TAG="${1:-test}"
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
REF="$REGISTRY/$OWNER/$IMAGE:$TAG"
|
||||
# buildx (BuildKit) is required: the Dockerfile uses RUN --mount=type=cache
|
||||
# for the uv dep cache, which the legacy builder rejects.
|
||||
docker buildx build --load -t "$REF" .
|
||||
docker push "$REF"
|
||||
|
||||
echo "Pushed $REF"
|
||||
@@ -0,0 +1,46 @@
|
||||
; Runs inside the single app container as the non-root "app" user (see
|
||||
; Dockerfile's USER app - no [supervisord] user= needed, we never start as
|
||||
; root here). Unlike deploy/supervisor/polylan-submitter.conf (multiple
|
||||
; daphne processes behind HAProxy on one bare-metal host), this container
|
||||
; runs ONE daphne process on TCP: scale by running more container replicas
|
||||
; behind an external reverse proxy/load balancer instead of more processes
|
||||
; per container.
|
||||
;
|
||||
; Program logs go to stdout/stderr (logfile_maxbytes=0 disables supervisor's
|
||||
; own log rotation for them) so `docker logs` / Proxmox's log viewer picks
|
||||
; them up - no log files inside the container to manage.
|
||||
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
pidfile=/tmp/supervisord.pid
|
||||
logfile=/dev/null
|
||||
logfile_maxbytes=0
|
||||
|
||||
[program:web]
|
||||
command=/app/.venv/bin/daphne -b 0.0.0.0 -p 8000 polylan_submitter.asgi:application
|
||||
directory=/app/polylan_submitter
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopasgroup=true
|
||||
killasgroup=true
|
||||
stopsignal=TERM
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
; RQ workers - same command as deploy/supervisor/polylan-submitter.conf.
|
||||
[program:worker]
|
||||
command=/app/.venv/bin/python manage.py rqworker default --job-class django_tasks_rq.Job --with-scheduler
|
||||
directory=/app/polylan_submitter
|
||||
process_name=%(program_name)s-%(process_num)02d
|
||||
numprocs=2
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopasgroup=true
|
||||
killasgroup=true
|
||||
stopsignal=TERM
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
Reference in New Issue
Block a user