start project
CI / Backend (lint, types, tests) (push) Has been cancelled
CI / Frontend (lint, types, build) (push) Has been cancelled
CI / Docker images build (push) Has been cancelled

This commit is contained in:
2026-09-04 12:52:21 +03:30
parent a464e550c9
commit 212074c5f5
40 changed files with 2804 additions and 164 deletions
+45 -6
View File
@@ -50,16 +50,33 @@ RUN pip install --no-index --find-links=/wheels -r /tmp/requirements/base.txt \
&& rm -rf /wheels /tmp/requirements
WORKDIR /app
COPY --chown=app:app backend/ /app/
# Explicit list, not `COPY backend/ /app/`: the runtime image must not ship
# test code (or stray repo files like this Dockerfile itself), only what the
# server needs to run. `tests/` is added back for the `dev` stage below.
COPY --chown=app:app backend/apps/ /app/apps/
COPY --chown=app:app backend/config/ /app/config/
COPY --chown=app:app backend/manage.py backend/entrypoint.sh backend/gunicorn.conf.py backend/pyproject.toml /app/
COPY --chown=app:app data/ /data/
# Static assets are only needed for the admin and the API docs, but baking them
# in keeps the container stateless.
RUN DJANGO_SECRET_KEY=build-only-not-a-real-secret-000000000000000000000000000000000000 \
# in keeps the container stateless. The step runs against the production
# settings on purpose: they are the ones this image ships with, and the dev
# module pulls in django-extensions which is not part of base.txt.
# WORKDIR creates /app as root before the COPY above populates it, and
# --chown on COPY only touches what it copies - the directory entry itself
# stays root-owned, which is enough to make anything that writes a *new* file
# straight into /app (e.g. pytest-cov's .coverage) fail with EACCES as the
# app user.
# The secret is generated per build and thrown away with the layer: nothing
# secret-shaped is ever committed to the repo or baked into the image, and
# collectstatic only needs *a* valid key, never a specific one. (A literal
# placeholder here would also trip the guard in config/settings/prod.py.)
RUN chown app:app /app \
&& chmod +x /app/entrypoint.sh \
&& DJANGO_SECRET_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(64))')" \
DATABASE_URL=postgres://build:build@localhost:5432/build \
DJANGO_SETTINGS_MODULE=config.settings.dev \
python manage.py collectstatic --noinput --clear \
&& chmod +x /app/entrypoint.sh
DJANGO_SETTINGS_MODULE=config.settings.prod \
python manage.py collectstatic --noinput --clear
USER app
EXPOSE 8000
@@ -69,3 +86,25 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["gunicorn", "config.wsgi:application", "--config", "/app/gunicorn.conf.py"]
# -----------------------------------------------------------------------------
# Stage 3 - development
#
# Same layout as the runtime image plus the tooling from dev.txt (pytest,
# django-extensions, ...) so `config.settings.dev` can actually be imported.
# Selected through `target: dev` in docker-compose.dev.yml.
# -----------------------------------------------------------------------------
FROM runtime AS dev
USER root
ENV DJANGO_SETTINGS_MODULE=config.settings.dev
COPY backend/requirements/ /tmp/requirements/
RUN pip install -r /tmp/requirements/dev.txt \
&& rm -rf /tmp/requirements
# The one directory the runtime stage deliberately left out.
COPY --chown=app:app backend/tests/ /app/tests/
USER app