|
| 1 | +# syntax=docker/dockerfile:1.7 |
| 2 | +# Default image for the SimpleModule reference app — the host plus the bundled |
| 3 | +# modules that a single web process needs (auth/users, dashboard, permissions, |
| 4 | +# settings, file storage, feature flags, audit log, branding, site lock). |
| 5 | +# |
| 6 | +# Background tasks are deliberately not part of it: Celery is a second process |
| 7 | +# plus a broker, which is the opposite of a standalone image. The worker/beat |
| 8 | +# services in docker-compose.yml build docker/worker.Dockerfile for that. |
| 9 | +# |
| 10 | +# docker build -t simple-module-python . |
| 11 | +# docker run --rm -p 8000:8000 simple-module-python |
| 12 | +# |
| 13 | +# Standalone by design: SQLite under /app/data, no Postgres and no Redis |
| 14 | +# needed to boot. `docker-compose.yml`'s `app` service is the same image with |
| 15 | +# a named volume; worker/beat (Celery) stay opt-in. |
| 16 | +# |
| 17 | +# One builder stage carries both uv and Node because the Vite build imports |
| 18 | +# `modules.generated.{ts,css}`, which `smpy host gen-pages` emits from the |
| 19 | +# *installed Python modules* — a Node-only stage would have nothing to read. |
| 20 | + |
| 21 | +FROM ghcr.io/astral-sh/uv:python3.12-bookworm AS builder |
| 22 | + |
| 23 | +ENV UV_LINK_MODE=copy \ |
| 24 | + UV_COMPILE_BYTECODE=1 \ |
| 25 | + PYTHONUNBUFFERED=1 |
| 26 | + |
| 27 | +WORKDIR /app |
| 28 | + |
| 29 | +# Node 24 — same major as NODE_VERSION in .github/workflows/pr.yml, so the |
| 30 | +# image builds the bundle CI validates. |
| 31 | +RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ |
| 32 | + && apt-get install -y --no-install-recommends nodejs \ |
| 33 | + && rm -rf /var/lib/apt/lists/* |
| 34 | + |
| 35 | +# Dependency layer: every workspace member's manifest, resolved before the |
| 36 | +# full source arrives. `uv.lock` is gitignored in this repo, so it's an |
| 37 | +# optional glob and the sync deliberately isn't `--frozen`. |
| 38 | +COPY pyproject.toml uv.lock* ./ |
| 39 | +COPY framework/ framework/ |
| 40 | +COPY modules/ modules/ |
| 41 | +COPY host/pyproject.toml host/ |
| 42 | +RUN --mount=type=cache,target=/root/.cache/uv \ |
| 43 | + uv sync --all-packages --no-dev --no-install-workspace |
| 44 | + |
| 45 | +# npm workspaces span host/client_app, packages/* and modules/* — every |
| 46 | +# member's package.json must exist before `npm ci` will honour the lockfile. |
| 47 | +COPY package.json package-lock.json ./ |
| 48 | +COPY packages/ packages/ |
| 49 | +COPY host/client_app/package.json host/client_app/ |
| 50 | +RUN --mount=type=cache,target=/root/.npm npm ci |
| 51 | + |
| 52 | +# --no-install-package drops the Celery module from this image: with no entry |
| 53 | +# point installed, discovery never sees it, so nothing here needs a broker and |
| 54 | +# the bundle carries none of its pages. Drop the flag (and point |
| 55 | +# SM_BG_TASKS_BROKER_URL at a real Redis) to run tasks from the web process. |
| 56 | +COPY . . |
| 57 | +RUN --mount=type=cache,target=/root/.cache/uv \ |
| 58 | + uv sync --all-packages --no-dev --no-install-package simple-module-background-tasks |
| 59 | + |
| 60 | +# Page manifest + generated module imports first, then the production bundle |
| 61 | +# into host/static/dist (with its .vite/manifest.json and precompressed |
| 62 | +# .gz/.br siblings, which the host serves from the /static mount). |
| 63 | +# The venv binary directly rather than `uv run`, which re-resolves and re-syncs |
| 64 | +# the environment on every invocation — the layer above already installed |
| 65 | +# exactly what this image should contain. |
| 66 | +RUN /app/.venv/bin/smpy host gen-pages --host-dir=host/client_app |
| 67 | +RUN npm run build |
| 68 | + |
| 69 | +# node_modules is a build-time artifact only; the runtime serves static files. |
| 70 | +RUN rm -rf node_modules host/client_app/node_modules |
| 71 | + |
| 72 | +FROM python:3.12-slim-bookworm AS runtime |
| 73 | + |
| 74 | +ENV PYTHONUNBUFFERED=1 \ |
| 75 | + PYTHONDONTWRITEBYTECODE=1 \ |
| 76 | + PATH="/app/.venv/bin:$PATH" |
| 77 | + |
| 78 | +# Containers serve the built bundle; development mode would emit asset tags |
| 79 | +# pointing at a Vite dev server that isn't in this image. |
| 80 | +ENV SM_ENVIRONMENT=production |
| 81 | + |
| 82 | +# Absolute sqlite path: /app/data is the volume mount point, so the DB is |
| 83 | +# cwd-independent and survives restarts whenever a volume is attached. |
| 84 | +ENV SM_DATABASE_URL=sqlite+aiosqlite:////app/data/app.db |
| 85 | + |
| 86 | +# curl backs the HEALTHCHECK below. |
| 87 | +RUN apt-get update \ |
| 88 | + && apt-get install -y --no-install-recommends curl ca-certificates \ |
| 89 | + && rm -rf /var/lib/apt/lists/* |
| 90 | + |
| 91 | +COPY --from=builder /app /app |
| 92 | + |
| 93 | +RUN mkdir -p /app/data \ |
| 94 | + && useradd --system --uid 10001 --home /app --shell /usr/sbin/nologin app \ |
| 95 | + && chown -R app:app /app |
| 96 | +USER app |
| 97 | + |
| 98 | +WORKDIR /app |
| 99 | +EXPOSE 8000 |
| 100 | +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ |
| 101 | + CMD curl -fsS http://localhost:8000/health || exit 1 |
| 102 | + |
| 103 | +ENTRYPOINT ["/app/docker/entrypoint.sh"] |
| 104 | +CMD ["uvicorn", "host.main:app", "--host", "0.0.0.0", "--port", "8000"] |
0 commit comments