From 34b6c8d94519cde290e4c290841e1655d2e7e7e2 Mon Sep 17 00:00:00 2001 From: Bert Hausmans Date: Thu, 21 May 2026 09:51:52 +0200 Subject: [PATCH] chore(deploy): Dockerfile + Dockge compose + deploy guide --- .dockerignore | 15 ++++++++ DEPLOY.md | 88 ++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 32 ++++++++++++++++ compose.yaml | 42 +++++++++++++++++++++ docker-entrypoint.sh | 9 +++++ 5 files changed, 186 insertions(+) create mode 100644 .dockerignore create mode 100644 DEPLOY.md create mode 100644 Dockerfile create mode 100644 compose.yaml create mode 100755 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c64d455 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +**/node_modules +**/dist +**/.vite +data +**/data/*.db +**/data/*.db-* +e2e +**/test-results +**/playwright-report +docs +*.log +.env +.env.local +**/*.backup-* diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..0e99c09 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,88 @@ +# Deploy to Dockge (fastest path: build on the host) + +This is the quickest way to get Flashcard running on your Proxmox + Dockge setup. +Source comes from Gitea; Dockge builds the image locally and runs it. No container +registry, no Gitea Actions, no `docker login` required. + +Repo: `https://gitea.hausmans.cloud/bert.hausmans/flashcards` + +## What gets deployed + +One container (`flashcard`) serving the API **and** the built frontend on port +`3000`, with a persistent named volume (`flashcard-data`) for the SQLite database. +Migrations run automatically on every container start. + +## Step 1 — push the code to Gitea (from your dev machine) + +```bash +cd /Users/berthausmans/Documents/Development/flashcard +git remote add origin https://gitea.hausmans.cloud/bert.hausmans/flashcards.git # first time only +git push -u origin master +``` + +## Step 2 — put the stack on the Dockge host + +SSH into the host running Dockge. Dockge keeps stacks in `/opt/stacks` by default. + +```bash +cd /opt/stacks +git clone https://gitea.hausmans.cloud/bert.hausmans/flashcards.git flashcard +``` + +A "flashcard" stack now appears in the Dockge UI (it reads `compose.yaml`). + +## Step 3 — configure environment in Dockge + +Open the **flashcard** stack in Dockge and edit the `environment:` block: + +- `APP_URL` → how you reach the app, e.g. `http://:3000` + (this is what verification/reset/invite e-mail links use — get it right). +- `COOKIE_SECURE` → leave `"false"` for now (plain HTTP). Flip to `"true"` only + after you put the app behind HTTPS. +- SMTP (Amazon SES) → fill in `SMTP_HOST` (e.g. `email-smtp.eu-west-1.amazonaws.com`), + `SMTP_USER`, `SMTP_PASS`, and `SMTP_FROM`. **Optional for first boot** — see Step 5. + +## Step 4 — deploy + +Click **Deploy** (Dockge runs `docker compose up -d --build`). First build takes a +few minutes (installs deps, builds the frontend, compiles better-sqlite3). When it's +up, open `http://:3000`. + +## Step 5 — create the first account (becomes sysadmin) + +Go to `/register` and sign up. The **first registered user is automatically the +sysadmin**. + +- If SES is configured: click the verification link in your inbox. +- If you left SMTP empty: grab the verification link from the container logs — + in Dockge open the flashcard stack → Logs, and copy the + `…/verify-email?token=…` URL printed by the stub mailer. + +Then log in. Done. + +## Updating later + +```bash +cd /opt/stacks/flashcard +git pull +``` + +Then in Dockge click **Deploy** again (rebuilds + restarts). The `flashcard-data` +volume — and therefore all lessons, cards and accounts — persists across redeploys. + +## Notes & gotchas + +- **Data safety:** everything lives in the `flashcard-data` Docker volume. Don't + delete it. Back it up by copying `/data/flashcard.db` out of the container/volume. +- **Secure cookies need HTTPS.** With `COOKIE_SECURE=true` over plain HTTP the + browser silently discards the login cookie. Keep it `false` until you have TLS. +- **Mailpit** (`docker-compose.yml`) is dev-only and is ignored here — Dockge uses + `compose.yaml`. +- The backend runs via `tsx` (TypeScript runtime), matching dev. Migrations are + applied automatically by `docker-entrypoint.sh` before the server starts. + +## Next step (optional, when you have time) + +Move to the Gitea container registry + Gitea Actions so Dockge pulls a pre-built +image instead of building on the host. Ask and I'll add a `.gitea/workflows/build.yml` +plus a registry-based `compose.yaml`. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f6c4050 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Flashcard — single-container production image. +# Runs the Express API and serves the built React frontend on PORT (default 3000). +# +# The backend is launched with tsx (TypeScript runtime) because the shared +# package is consumed as TypeScript source; this matches how the app runs in dev +# and avoids a separate compile step for the shared workspace. +FROM node:22-bookworm-slim + +# better-sqlite3 is a native module. Provide a toolchain in case no prebuilt +# binary exists for the target platform (most amd64/arm64 hosts use a prebuild). +RUN apt-get update \ + && apt-get install -y --no-install-recommends python3 make g++ ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Install dependencies first for better layer caching. +# Do NOT set NODE_ENV=production here: the image needs devDependencies +# (vite to build the frontend, tsx to run the backend). +COPY package.json package-lock.json ./ +COPY packages/shared/package.json packages/shared/package.json +COPY packages/backend/package.json packages/backend/package.json +COPY packages/frontend/package.json packages/frontend/package.json +RUN npm ci + +# Copy the source and build the static frontend (served by the backend). +COPY . . +RUN npm -w @flashcard/frontend run build \ + && chmod +x /app/docker-entrypoint.sh + +EXPOSE 3000 +ENTRYPOINT ["/app/docker-entrypoint.sh"] diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..dc3f332 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,42 @@ +# Production stack for Dockge — builds the image on the host from this repo. +# Dockge prefers compose.yaml, so this file is used instead of the dev +# docker-compose.yml (which only runs Mailpit for local development). +services: + flashcard: + build: + context: . + dockerfile: Dockerfile + image: flashcard:local + container_name: flashcard + restart: unless-stopped + ports: + - "3000:3000" + volumes: + - flashcard-data:/data + environment: + NODE_ENV: production + PORT: "3000" + DB_PATH: /data/flashcard.db + + # URL where users reach the app. Used in verification / reset / invite emails. + # CHANGE THIS to your real address, e.g. http://192.168.1.50:3000 + # or https://flashcards.hausmans.cloud once behind a reverse proxy. + APP_URL: "http://CHANGE-ME:3000" + + # Cookie security. Keep "false" when serving over plain HTTP — otherwise the + # browser drops the session cookie and login silently fails. Set to "true" + # only once the app is served over HTTPS (reverse proxy with TLS). + COOKIE_SECURE: "false" + + # SMTP (Amazon SES). Leave SMTP_HOST empty to fall back to a stub mailer that + # prints verification/reset links to the container logs — handy for the very + # first sysadmin signup before SES is wired up. + SMTP_HOST: "" + SMTP_PORT: "587" + SMTP_SECURE: "false" + SMTP_USER: "" + SMTP_PASS: "" + SMTP_FROM: "Flashcard " + +volumes: + flashcard-data: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..1db10cc --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e +cd /app + +echo "[entrypoint] Applying database migrations…" +node_modules/.bin/tsx packages/backend/src/db/migrate.ts + +echo "[entrypoint] Starting Flashcard on port ${PORT:-3000}…" +exec node_modules/.bin/tsx packages/backend/src/index.ts