Reverses the placement decision made an hour ago. That one put the APK only on the data volume, reasoning that ~55 MiB should not be charged to installs that never touch Android. The operator's call is that ending the manual copy is worth the megabytes, and it is their deployment. CI now fetches the newest published client into the build context immediately before the image build, so `:dev`, `:latest` and `:<version>` all ship one and a `docker compose pull` delivers a new server and a new client together. **Always the rolling `dev` release — the newest build there is.** A versioned image therefore carries the newest client rather than one pinned to that version. Deliberate: the two negotiate a sync protocol version before linking, so a mismatch is caught by the handshake, and pinning would buy nothing the handshake does not already provide. **Fetched by the JOB, never by the Dockerfile.** The release is private, and a token used inside a build ends up in the context or a layer. **It cannot fail the image build.** No release yet, a network blip, a first-ever build — all of them log a warning and produce an image with no client, which is a state the server already supports. Half a pair is cleaned up rather than shipped: a sidecar without its APK is worse than neither, because the server would be describing something it cannot serve. **The volume still wins.** `DATA_DIR/client/` is checked first and the baked copy second, so an operator who deliberately drops a build in gets that build — and a BROKEN drop-in falls through to the image's copy rather than taking the feature offline, which is what makes the copy-order advice survivable instead of load-bearing. Three tests cover the precedence, including that last case. The baked copy lives inside the package, not under DATA_DIR: that path is a volume mount, and anything the image wrote there would disappear behind it the moment one is attached. `client/.keep` is tracked so `COPY client/` cannot fail on a tree where the CI step never ran; the artifacts themselves are gitignored, since a 55 MiB binary does not belong in git history and is re-fetched on every build anyway.
50 lines
1.9 KiB
Docker
50 lines
1.9 KiB
Docker
# syntax=docker/dockerfile:1
|
|
|
|
# Stage 1: build the Vue frontend (also type-checks via `vue-tsc` in the build script).
|
|
FROM node:22-alpine AS build-frontend
|
|
WORKDIR /build
|
|
COPY frontend/package.json frontend/package-lock.json* ./
|
|
RUN npm ci --quiet
|
|
COPY frontend/ .
|
|
RUN npm run build
|
|
|
|
# Stage 2: Python runtime.
|
|
FROM python:3.12-slim AS runtime
|
|
WORKDIR /app
|
|
|
|
COPY pyproject.toml .
|
|
COPY src/ src/
|
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
|
pip install .
|
|
|
|
# Bake the built SPA into the package's static dir (served by app.py). PYTHONPATH
|
|
# points at /app/src so the runtime imports this source tree (with static/ present),
|
|
# not the pip-installed copy.
|
|
COPY --from=build-frontend /build/dist/ src/thoughtsync/static/
|
|
COPY alembic.ini .
|
|
COPY alembic/ alembic/
|
|
|
|
# The Android client this server hands out. CI fetches the newest published build
|
|
# into ./client immediately before this runs (ci.yml), so every image tag — :dev,
|
|
# :latest and :<version> alike — ships a client, and a `docker compose pull`
|
|
# delivers a new one with no file copying by hand.
|
|
#
|
|
# Fetched by the JOB rather than here on purpose: the release is private, and a
|
|
# token used inside a build ends up in the build context or a layer.
|
|
#
|
|
# The directory is tracked (client/.keep) so this COPY cannot fail on a tree where
|
|
# that step never ran. An image with no APK is a supported state — the server
|
|
# advertises nothing and the web UI hides the download (client_dist.py).
|
|
COPY client/ src/thoughtsync/client/
|
|
|
|
ENV PYTHONPATH=/app/src
|
|
|
|
ARG BUILD_VERSION=dev
|
|
ENV APP_VERSION=$BUILD_VERSION
|
|
|
|
EXPOSE 5000
|
|
# Wait for the database, run migrations, then serve. The DB wait keeps a briefly
|
|
# slow/unready database from crash-looping the container. Family convention
|
|
# (rule 82): schema is built by real migrations, never metadata.create_all.
|
|
CMD ["sh", "-c", "python -m thoughtsync.dbwait && alembic upgrade head && hypercorn 'thoughtsync.app:create_app()' --bind 0.0.0.0:5000 --keep-alive 600"]
|