CI / Build and push docker image (push) Successful in 1m54s
Add a web app manifest and an install-only service worker (no offline caching: live state always comes from the server and hashed asset names change every build), registered from the WASM bundle. The worker must live at the site root for its scope to cover the app, and Granian only serves /static and /assets, so tavolo/static.py answers /manifest.webmanifest and /sw.js from STATIC_DIR with the content types browsers require; index.html gains the manifest link, icons, theme-color and Apple standalone meta tags. Icons are committed under web/assets/icons/ with vector sources and a make-icons.sh regeneration script, mirroring the card art workflow.
118 lines
5.1 KiB
Docker
118 lines
5.1 KiB
Docker
# syntax=docker/dockerfile:1
|
|
# Multi-stage build for the tavolo stack (kaya backend + Sycamore/WASM
|
|
# frontend). The Docker build context is the REPOSITORY ROOT (see
|
|
# docker-compose.yml) so this single image assembles both parts:
|
|
#
|
|
# web-builder rust + trunk -> compiles web/ into web/dist
|
|
# builder alpine python -> python venv with the backend + deps
|
|
# runtime alpine python + the venv + the compiled frontend
|
|
#
|
|
# apk and pip both use BuildKit cache mounts (type=cache): the package
|
|
# caches persist in the builder's cache across builds instead of being
|
|
# re-downloaded, and never land in the image layers.
|
|
|
|
# --- Web builder -------------------------------------------------------------
|
|
FROM rust:1-slim AS web-builder
|
|
|
|
ARG TRUNK_VERSION=0.21.14
|
|
# The build environment blocks plain HTTP: force the apt mirrors to HTTPS.
|
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
|
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
|
sed -i 's|http://deb.debian.org|https://deb.debian.org|g' /etc/apt/sources.list.d/debian.sources \
|
|
&& apt-get update \
|
|
&& apt-get install -y --no-install-recommends curl ca-certificates \
|
|
&& curl -fsSL "https://github.com/trunk-rs/trunk/releases/download/v${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz" \
|
|
| tar -xz -C /usr/local/bin \
|
|
&& rustup target add wasm32-unknown-unknown
|
|
|
|
WORKDIR /web
|
|
|
|
# Card images are committed in the repository (CC0 woodcut napoletane deck);
|
|
# web/fetch-cards.sh can regenerate them.
|
|
COPY web/Cargo.toml web/Cargo.lock web/index.html web/style.css web/theme.css web/Trunk.toml ./
|
|
# PWA manifest and service worker: trunk's copy-file entries need them at
|
|
# the build root, and the runtime serves both from the site root (static.py).
|
|
COPY web/manifest.webmanifest web/sw.js ./
|
|
COPY web/assets ./assets
|
|
# Vendored Bootstrap (MIT) loaded by index.html; kept out of the CDN so the
|
|
# image builds and runs without network access.
|
|
COPY web/vendor ./vendor
|
|
COPY web/src ./src
|
|
|
|
# --public-url makes trunk emit asset URLs under /static, the prefix Granian
|
|
# serves in the runtime image (see GRANIAN_STATIC_PATH_* below). Dev builds
|
|
# (trunk serve) keep the default "/" public URL.
|
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|
--mount=type=cache,target=/web/target \
|
|
trunk build --release --public-url /static/
|
|
|
|
# --- Python builder ----------------------------------------------------------
|
|
FROM alpine:3.24 AS builder
|
|
|
|
RUN --mount=type=cache,target=/var/cache/apk \
|
|
apk add python3 py3-pip build-base python3-dev
|
|
|
|
WORKDIR /build
|
|
|
|
|
|
COPY server/requirements.txt ./
|
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
|
python3 -m venv /opt/venv \
|
|
&& /opt/venv/bin/pip install --upgrade pip \
|
|
&& /opt/venv/bin/pip install -r requirements.txt
|
|
|
|
# The five sibling distributions, installed from the local tree (PEP 420
|
|
# namespace packages sharing the `tavolo` namespace). --no-deps: every
|
|
# third-party dependency — including the tavolo-app `otel` extra — is
|
|
# already pinned in requirements.txt.
|
|
COPY server/packages/ ./packages/
|
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
|
/opt/venv/bin/pip install --no-deps ./packages/tavolo-platform ./packages/tavolo-scopone ./packages/tavolo-scopa ./packages/tavolo-briscola ./packages/tavolo-app
|
|
# server/pyproject.toml only carries the [tool.aerich] section (the
|
|
# db-migrate compose service runs `aerich upgrade` with working_dir=/app,
|
|
# next to ./migrations).
|
|
COPY server/pyproject.toml ./
|
|
# aerich migration files are a release artifact: the db-migrate compose
|
|
# service runs `aerich upgrade` from this image before the app starts.
|
|
COPY server/migrations/ ./migrations/
|
|
|
|
# --- Runtime ---------------------------------------------------------------
|
|
FROM alpine:3.24
|
|
|
|
RUN --mount=type=cache,target=/var/cache/apk \
|
|
apk add python3 ca-certificates tzdata \
|
|
&& addgroup -S app && adduser -S -G app app
|
|
|
|
COPY --from=builder /opt/venv /opt/venv
|
|
COPY --from=builder /build/migrations /app/migrations
|
|
# aerich reads [tool.aerich] from pyproject.toml (its default config file);
|
|
# the db-migrate compose service runs `aerich upgrade` with working_dir=/app.
|
|
COPY --from=builder /build/pyproject.toml /app/pyproject.toml
|
|
# The compiled single-page application. Granian serves the assets directly
|
|
# in Rust — hashed js/wasm/css under /static/* and the card images under
|
|
# /assets/* (see the GRANIAN_STATIC_PATH_* env vars below; click splits
|
|
# multi-value env vars on whitespace for routes and ':' for paths). The
|
|
# Python app only serves the SPA shell (index.html) at / and for
|
|
# client-side routes (STATIC_DIR).
|
|
COPY --from=web-builder /web/dist /app/web/dist
|
|
|
|
ENV PATH="/opt/venv/bin:$PATH" \
|
|
PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
GRANIAN_HOST=0.0.0.0 \
|
|
GRANIAN_PORT=8080 \
|
|
GRANIAN_INTERFACE=rsgi \
|
|
GRANIAN_STATIC_PATH_ROUTE="/static /assets" \
|
|
GRANIAN_STATIC_PATH_MOUNT="/app/web/dist:/app/web/dist/assets" \
|
|
GRANIAN_STATIC_PATH_DIR_TO_FILE=index.html \
|
|
STATIC_DIR=/app/web/dist
|
|
|
|
USER app
|
|
|
|
EXPOSE 8080
|
|
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
|
|
CMD wget -q -O- http://127.0.0.1:8080/api/health || exit 1
|
|
|
|
CMD ["granian", "tavolo.app:app"]
|