updating documentation
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m3s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m3s
This commit is contained in:
parent
201f462447
commit
e5595b5039
5 changed files with 268 additions and 61 deletions
|
|
@ -359,3 +359,71 @@ All deferred post-MVP, purely additive (new tables referencing existing `terms`)
|
|||
- `noun_forms` — gender, singular, plural, articles per language (source: Wiktionary)
|
||||
- `verb_forms` — conjugation tables per language (source: Wiktionary)
|
||||
- `term_pronunciations` — IPA and audio URLs per language (source: Wiktionary / Forvo)
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Reverse proxy: Caddy (not Nginx, not Traefik)
|
||||
|
||||
Caddy provides automatic HTTPS via Let's Encrypt with zero configuration beyond specifying domain names. The entire Caddyfile is ~10 lines. Nginx would require manual certbot setup and more verbose config. Traefik's auto-discovery of Docker containers (via labels) is powerful but overkill for a stable three-service stack where routing rules never change. Caddy runs as a Docker container alongside the app — no native install.
|
||||
|
||||
### Subdomain routing (not path-based)
|
||||
|
||||
`lilastudy.com` serves the frontend, `api.lilastudy.com` serves the API, `git.lilastudy.com` serves Forgejo. Cleaner separation than path-based routing — any service can be moved to a different server just by changing DNS. Requires CORS configuration since the browser sees different origins, and cross-subdomain cookies via `COOKIE_DOMAIN=.lilastudy.com`. Wildcard DNS (`*.lilastudy.com`) means new subdomains require no DNS changes.
|
||||
|
||||
### Frontend served by nginx:alpine (not Node, not Caddy)
|
||||
|
||||
Vite builds to static files. Serving them with nginx inside the container is lighter than running a Node process and keeps the container at ~7MB. Caddy could serve them directly, but using a separate container maintains the one-service-per-container principle and keeps Caddy's config purely about routing.
|
||||
|
||||
### SPA fallback via nginx `try_files`
|
||||
|
||||
Without `try_files $uri $uri/ /index.html`, refreshing on `/play` returns 404 because there's no actual `play` file. Nginx serves `index.html` for all routes and lets TanStack Router handle client-side routing.
|
||||
|
||||
### Forgejo as git server + container registry (not GitHub, not Docker Hub)
|
||||
|
||||
Keeps everything self-hosted on one VPS. Forgejo's built-in package registry doubles as a container registry, eliminating a separate service. Git push and image push go to the same server.
|
||||
|
||||
### Forgejo SSH on port 2222 (not 22)
|
||||
|
||||
Port 22 is the VPS's own SSH. Mapping Forgejo's SSH to 2222 avoids conflicts. Dev laptop `~/.ssh/config` maps `git.lilastudy.com` to port 2222 so git commands work without specifying the port every time.
|
||||
|
||||
### `packages/db` and `packages/shared` exports: compiled JS paths
|
||||
|
||||
Exports in both package.json files point to `./dist/src/index.js`, not TypeScript source. In dev, `tsx` can run TypeScript, but in production Node cannot. This means packages must be built before the API starts in dev — acceptable since these packages change infrequently. Alternative approaches (conditional exports, tsconfig paths) were considered but added complexity for no practical benefit.
|
||||
|
||||
### Environment-driven config for production vs dev
|
||||
|
||||
CORS origin, Better Auth base URL, cookie domain, API URL, and OAuth credentials are all read from environment variables with localhost fallbacks. The same code runs in both environments without changes. `VITE_API_URL` is the exception — it's baked in at build time via Docker build arg because Vite replaces `import.meta.env` at compile time, not runtime.
|
||||
|
||||
### Cross-subdomain cookies
|
||||
|
||||
Better Auth's `defaultCookieAttributes` sets `domain: .lilastudy.com` in production (from env var `COOKIE_DOMAIN`). Without this, the auth cookie scoped to `api.lilastudy.com` wouldn't be sent on requests from `lilastudy.com`. The leading dot makes the cookie valid across all subdomains.
|
||||
|
||||
---
|
||||
|
||||
## CI/CD
|
||||
|
||||
### Forgejo Actions with SSH deploy (not webhooks, not manual)
|
||||
|
||||
CI builds images natively on the ARM64 VPS (no QEMU cross-compilation). The runner uses the host's Docker socket to build. After pushing to the registry, the workflow SSHs into the VPS to pull and restart containers. Webhooks were considered but add an extra listener service to maintain and secure. Manual deploy was the initial approach but doesn't scale with frequent pushes.
|
||||
|
||||
### Dedicated CI SSH key
|
||||
|
||||
A separate `ci-runner` SSH key pair (not the developer's personal key) is used for CI deploys. The private key is stored in Forgejo's secrets. If compromised, only this key needs to be revoked — the developer's access is unaffected.
|
||||
|
||||
### Runner config: `docker_host: "automount"` + `valid_volumes` + explicit config path
|
||||
|
||||
The Forgejo runner's `automount` setting mounts the host Docker socket into job containers. `valid_volumes` must include `/var/run/docker.sock` or the mount is blocked. The runner command must explicitly reference the config file (`-c /data/config.yml`) — without this flag, config changes are silently ignored. `--group-add 989` in container options adds the host's docker group so job containers can access the socket.
|
||||
|
||||
### Docker CLI installed per job (not baked into runner image)
|
||||
|
||||
The job container (`node:24-bookworm`) doesn't include Docker CLI. It's installed via `apt-get install docker.io` as the first workflow step. This adds ~20 seconds per run but avoids maintaining a custom runner image. The CLI sends commands through the mounted socket to the host's Docker engine.
|
||||
|
||||
---
|
||||
|
||||
## Backups
|
||||
|
||||
### pg_dump cron + dev laptop sync (not WAL archiving, not managed service)
|
||||
|
||||
Daily compressed SQL dumps with 7-day retention. Dev laptop auto-syncs new backups on login via rsync. Simple, portable, sufficient for current scale. WAL archiving gives point-in-time recovery but is complex to set up. Offsite storage (Hetzner Object Storage) is the planned next step — backups on the same VPS don't protect against VPS failure.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue