Files

105 lines
4.4 KiB
Docker
Raw Permalink Normal View History

# syntax=docker/dockerfile:1.7
#
# Packet Station image. One image runs the app, the worker, migrations and doctor.
# Build: docker build --build-arg PS_BUILD=sha-<sha> -t gt.pescoinc.biz/spencerm/packetstation:sha-<sha> .
# No password is ever passed at build time. Secrets arrive at run time as compose file secrets.
ARG PYTHON_IMAGE=python:3.12-slim-bookworm
ARG NODE_IMAGE=node:22-bookworm-slim
# ---------------------------------------------------------------------------
# Stage 1: frontend. Vite build into frontend/dist.
# ---------------------------------------------------------------------------
FROM ${NODE_IMAGE} AS frontend
WORKDIR /src/frontend
# Lockfile first so the npm ci layer is cached until dependencies change.
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci --no-audit --no-fund
COPY frontend/ ./
RUN npm run build
# ---------------------------------------------------------------------------
# Stage 2: builder. Python venv in /opt/venv with every dependency and the package.
# ---------------------------------------------------------------------------
FROM ${PYTHON_IMAGE} AS builder
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1 \
PYTHONDONTWRITEBYTECODE=1
# PESCO's private index on Gitea. It serves visualkit, mfileskit and their dependencies only.
ARG PESCO_INDEX_URL=https://gt.pescoinc.biz/api/packages/spencerm/pypi/simple/
RUN python -m venv /opt/venv
ENV PATH=/opt/venv/bin:$PATH
WORKDIR /src
# Public dependencies from PyPI.
COPY server/requirements.txt server/requirements.txt
RUN pip install -r server/requirements.txt
# PESCO libraries from the Gitea index only. Always --index-url, never --extra-index-url,
# so a public package with the same name can never be picked instead.
COPY server/requirements-pesco.txt server/requirements-pesco.txt
RUN pip install --index-url "${PESCO_INDEX_URL}" -r server/requirements-pesco.txt
# The package itself. Dependencies are already in the venv.
COPY pyproject.toml README* ./
COPY server/ server/
RUN pip install --no-deps . \
&& pip check
# ---------------------------------------------------------------------------
# Stage 3: runtime.
# ---------------------------------------------------------------------------
FROM ${PYTHON_IMAGE} AS runtime
# ODBC Driver 18 from Microsoft's Debian 12 repository, plus tini and tzdata.
# curl and gnupg are only needed to fetch the signing key, so they are removed again.
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends curl gnupg ca-certificates; \
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg; \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/debian/12/prod bookworm main" \
> /etc/apt/sources.list.d/microsoft-prod.list; \
apt-get update; \
ACCEPT_EULA=Y apt-get install -y --no-install-recommends \
msodbcsql18 unixodbc tini tzdata ca-certificates; \
apt-get purge -y --auto-remove curl gnupg; \
rm -rf /var/lib/apt/lists/*; \
odbcinst -q -d -n "ODBC Driver 18 for SQL Server"
# Service user. Group 10001 matches the host secret files (root:10001 0440).
RUN groupadd -g 10001 ps \
&& useradd -u 10001 -g 10001 -r -M -d /app -s /usr/sbin/nologin ps
COPY --from=builder /opt/venv /opt/venv
COPY --from=frontend /src/frontend/dist /app/static
COPY --chmod=0755 docker/entrypoint.sh /app/entrypoint.sh
# Guard against CRLF line endings from a Windows checkout.
RUN sed -i 's/\r$//' /app/entrypoint.sh
# /data/assets is a named volume. Docker copies this ownership into a new volume on first use.
# /backups is mounted read-only into the worker; it only needs to exist.
RUN mkdir -p /data/assets /backups \
&& chown 10001:10001 /data/assets \
&& chmod 0750 /data/assets
ARG PS_BUILD=dev
ENV PATH=/opt/venv/bin:$PATH \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PS_BUILD=${PS_BUILD} \
STATIC_DIR=/app/static \
ASSETS_DIR=/data/assets \
BACKUP_DIR=/backups
LABEL org.opencontainers.image.title="packetstation" \
org.opencontainers.image.source="https://gt.pescoinc.biz/spencerm/packetstation" \
org.opencontainers.image.version="${PS_BUILD}" \
biz.pescoinc.packetstation.build="${PS_BUILD}"
WORKDIR /app
USER ps
EXPOSE 8000
# No HEALTHCHECK here. Compose sets one per service (app and worker check different things).
ENTRYPOINT ["tini", "--", "/app/entrypoint.sh"]
CMD ["app"]