Deploy plumbing so Petal can run on the public VPS behind the Traefik already on that box, with vLLM reached over headscale. - Dockerfile: node build -> go build -> alpine runtime. CGO stays off (modernc SQLite is pure Go), so the runtime layer exists only for ffmpeg (read-aloud transcodes Piper's WAV) and tzdata (the companion's bedtime nag and night mode read the local clock). Runs as uid 10001 with /data as the single writable mount. - docker-compose.yml: Traefik labels following this host's convention (external `traefik` network, `web-secure` entrypoint, `default` cert resolver). Petal publishes no host port. ./data is a bind mount, not a named volume, so the nightly backup and a restore are reachable from the host. - Piper runs as two sibling containers rather than host systemd units. The plan assumed Piper was already installed on the VPS; it is not, the host has no lingering user session to keep user units alive, and containers keep the TTS ports on an internal network unreachable from anywhere but Petal. One image, voice chosen per service, model cached in a shared volume -- so the pt-PT voice is a new service, not a new image. - db.Backup + a `-backup` flag: VACUUM INTO, not a file copy. Petal runs in WAL mode, so the newest committed pages may live in petal.db-wal; copying the three files separately can capture a torn mid-checkpoint state. VACUUM INTO reads one coherent snapshot without taking a write lock, and emits a single file with no -wal/-shm companions. Refuses an existing destination so a failed run can't destroy the last good backup. - deploy/backup-petal.sh: nightly snapshot, compress, push to millenia over headscale with a post-transfer size check, prune both sides. - deploy/petal.env.example: LLM_TIMEOUT raised 30s -> 90s for the WAN+VPN round trip, since the voice and collocation passes send a whole document and the timeout is a hard deadline on Complete.
67 lines
2.1 KiB
Docker
67 lines
2.1 KiB
Docker
# Petal — multi-stage build producing the single self-contained binary.
|
|
#
|
|
# Stage 1 builds the frontend; stage 2 compiles the Go server with web/dist
|
|
# embedded (go:embed), so the runtime image carries one executable and no
|
|
# assets. modernc's SQLite is pure Go, so CGO stays off and the binary is
|
|
# static — the runtime layer exists only for ffmpeg (read-aloud transcodes
|
|
# Piper's WAV to mp3) and CA certificates.
|
|
|
|
# ---------- stage 1: frontend ----------
|
|
FROM node:22-alpine AS web
|
|
|
|
WORKDIR /src/web
|
|
|
|
# Install deps against the lockfile alone so this layer caches across source
|
|
# edits. The Hunspell dictionaries come from a devDependency, so a plain
|
|
# `npm ci` (not --omit=dev) is required for the spell checker to ship.
|
|
COPY web/package.json web/package-lock.json ./
|
|
RUN npm ci
|
|
|
|
COPY web/ ./
|
|
RUN npm run build
|
|
|
|
# ---------- stage 2: server ----------
|
|
FROM golang:1.25-alpine AS build
|
|
|
|
WORKDIR /src
|
|
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
COPY . .
|
|
# The build context's web/dist is gitignored and excluded by .dockerignore;
|
|
# take the freshly built one from stage 1 so go:embed picks it up.
|
|
COPY --from=web /src/web/dist ./web/dist
|
|
|
|
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/petal ./cmd/server
|
|
|
|
# ---------- stage 3: runtime ----------
|
|
FROM alpine:3.21
|
|
|
|
# ffmpeg: read-aloud pipes Piper's WAV through it to mp3/opus. tzdata: the
|
|
# companion's bedtime nag and night mode read the local clock, so the container
|
|
# needs a real timezone rather than bare UTC.
|
|
RUN apk add --no-cache ca-certificates ffmpeg tzdata \
|
|
&& adduser -D -u 10001 petal
|
|
|
|
WORKDIR /app
|
|
COPY --from=build /out/petal /app/petal
|
|
|
|
# Mount point for petal.db (+ -wal/-shm), the image store and the TTS cache.
|
|
RUN mkdir -p /data && chown -R petal:petal /data
|
|
VOLUME ["/data"]
|
|
|
|
USER petal
|
|
EXPOSE 8080
|
|
|
|
ENV PORT=8080 \
|
|
DATABASE_PATH=/data/petal.db \
|
|
IMAGE_DIR=/data/images \
|
|
TTS_CACHE_DIR=/data/tts
|
|
|
|
# Same endpoint Traefik and the uptime probe use; needs no session by design.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
CMD wget -qO- http://127.0.0.1:8080/api/health || exit 1
|
|
|
|
ENTRYPOINT ["/app/petal"]
|