# 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- -t gt.pescoinc.biz/spencerm/packetstation: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"]