-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
138 lines (109 loc) · 5.15 KB
/
Copy pathDockerfile
File metadata and controls
138 lines (109 loc) · 5.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# syntax=docker/dockerfile:1
# ---------------------------------------------------------------------------
# Frontend: build the SPA.
#
# The output is served by Django in the runtime image, which keeps the SPA and
# the API on one origin -- what the session-cookie and CSRF design assumes.
# VITE_API_BASE_URL is therefore a relative path: the app calls its own origin.
# ---------------------------------------------------------------------------
FROM node:26-slim AS frontend
# The workspace root, not website/: there is one lockfile for the website, the
# mobile app and packages/shared, and `npm ci` has to run where it lives.
WORKDIR /app
# Manifests first, so `npm ci` stays cached until a dependency actually
# changes. Every workspace's package.json has to be here even though only one
# is installed below -- `npm ci` verifies the lockfile against all of them and
# refuses to run if one is missing.
COPY package.json package-lock.json ./
COPY packages/shared/package.json packages/shared/
COPY website/package.json website/
COPY mobile/package.json mobile/
# Only the website's tree. Without `--workspace` this would also install React
# Native and every Expo module -- hundreds of megabytes of native code that no
# part of this image can run, in a stage whose only job is to produce
# website/dist.
RUN npm ci --workspace website --workspace @app/shared --include-workspace-root
# The shared package is source, not a build artefact: Vite compiles its
# TypeScript as part of the app. It has to be present before the build.
COPY packages/shared/ packages/shared/
COPY website/ website/
WORKDIR /app/website
ARG VITE_API_BASE_URL=/api/v1
ARG VITE_STRIPE_ENABLED=false
ARG VITE_ORGANIZATIONS_ENABLED=false
ARG VITE_SOCIAL_AUTH_ENABLED=false
ARG VITE_TWO_FACTOR_ENABLED=false
ARG VITE_API_KEYS_ENABLED=false
ARG VITE_UPLOADS_ENABLED=false
ARG VITE_AUDIT_LOG_ENABLED=false
ARG VITE_STRIPE_PUBLISHABLE_KEY=
ENV VITE_API_BASE_URL=${VITE_API_BASE_URL} \
VITE_STRIPE_ENABLED=${VITE_STRIPE_ENABLED} \
VITE_ORGANIZATIONS_ENABLED=${VITE_ORGANIZATIONS_ENABLED} \
VITE_SOCIAL_AUTH_ENABLED=${VITE_SOCIAL_AUTH_ENABLED} \
VITE_TWO_FACTOR_ENABLED=${VITE_TWO_FACTOR_ENABLED} \
VITE_API_KEYS_ENABLED=${VITE_API_KEYS_ENABLED} \
VITE_UPLOADS_ENABLED=${VITE_UPLOADS_ENABLED} \
VITE_AUDIT_LOG_ENABLED=${VITE_AUDIT_LOG_ENABLED} \
VITE_STRIPE_PUBLISHABLE_KEY=${VITE_STRIPE_PUBLISHABLE_KEY}
RUN npm run build
# ---------------------------------------------------------------------------
# Builder: compile wheels for every dependency.
#
# Kept separate so the runtime image carries no compilers and no build headers.
# ---------------------------------------------------------------------------
FROM python:3.14-slim AS builder
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
RUN apt-get update && apt-get install --no-install-recommends -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Requirements are copied on their own so the wheel build is cached until a
# dependency actually changes, not on every source edit.
COPY requirements/ /app/requirements/
COPY requirements.txt /app/
ARG REQUIREMENTS=requirements/prod.txt
RUN python -m venv /opt/venv \
&& /opt/venv/bin/pip install --upgrade pip \
&& /opt/venv/bin/pip install -r ${REQUIREMENTS}
# ---------------------------------------------------------------------------
# Runtime
# ---------------------------------------------------------------------------
FROM python:3.14-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \
DJANGO_SETTINGS_MODULE=template.settings
# libpq is needed at runtime; the -dev headers and compilers are not.
RUN apt-get update && apt-get install --no-install-recommends -y \
libpq5 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Run as an unprivileged user.
RUN groupadd --system app && useradd --system --gid app --create-home app
COPY --from=builder /opt/venv /opt/venv
WORKDIR /app
COPY --chown=app:app . /app
# The built SPA. Django serves it from here; see SPA_DIST_DIR in settings.
COPY --from=frontend --chown=app:app /app/website/dist /app/website/dist
RUN chmod +x /app/entrypoint.sh
# Static files are collected at build time -- it needs no database, only
# settings that import cleanly. Migrations are NOT run here: they need a live
# database, which does not exist during a build. entrypoint.sh runs them.
# DATABASE_URL is parsed but never connected to -- production settings refuse
# to fall back to a localhost database, and collectstatic still needs them to
# import cleanly.
RUN SECRET_KEY=build-only-not-used-at-runtime \
DJANGO_ENVIRONMENT=production \
ALLOWED_HOSTS=localhost \
DATABASE_URL=postgres://build:build@db.invalid:5432/build \
FRONTEND_URL=https://build.invalid \
python manage.py collectstatic --noinput
USER app
EXPOSE 8000
ENTRYPOINT ["/app/entrypoint.sh"]
# Shell form so ${PORT} expands: managed hosts assign the port and route to it.
CMD ["sh", "-c", "gunicorn template.asgi:application -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:${PORT:-8000} --workers ${WEB_CONCURRENCY:-3} --access-logfile - --error-logfile -"]