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