Files
dvla-api/SERVER.md
Ovidiu U bf3aa57ef9 Add SERVER.md documentation for production VPS configuration
Documents Hetzner CX23 hosting setup, nginx/PHP-FPM stack, SQLite database configuration, and deployment architecture for single-app deployment
2026-05-15 14:00:44 +01:00

93 lines
3.5 KiB
Markdown

# Server — dvla-api
Production host for the `dvla-api` Laravel application. Single VPS, single app.
This file is the source of truth for how the box is configured. Update it when
you change the box.
## Host
- **Provider / type:** Hetzner Cloud CX23 (2 vCPU, 4 GB RAM, 80 GB disk)
- **OS:** Ubuntu 24.04 LTS (noble)
- **Public IP:** 37.27.203.46
- **Hostname:** ubuntu-4gb-dvla-api
- **Swap:** 2 GB swapfile at `/swapfile` (mounted via `/etc/fstab`)
## Users
- `root` — break-glass admin only
- `deploy` (UID 1000) — owns the app directory, performs deployments
- `www-data` — nginx + PHP-FPM runtime user; group-shared with `deploy` for file access
> **Note:** This box runs **one** app. If a second app is ever added, the
> default PHP-FPM `www` pool MUST be split into per-app pools running as
> per-app system users, or the apps will be able to read each other's `.env`
> and SQLite files. See the earlier project chats for the multi-app layout.
## Web stack
- **nginx** 1.24 — site config: `/etc/nginx/sites-available/dvla-api`
- Listens on `:80` (HTTP only — TLS pending)
- `server_name 37.27.203.46;`
- Document root: `/var/www/dvla-api/public`
- **PHP-FPM** 8.4 — pool: default `www.conf`
- Runs as `www-data:www-data`
- Socket: `/run/php/php8.4-fpm.sock`
- `pm = dynamic`, `pm.max_children = 5` (CX23-appropriate)
## Application
- **Path:** `/var/www/dvla-api`
- **Owner:** `deploy:www-data`, directories have SGID bit (`drwxrwsr-x`) so
new files inherit the `www-data` group automatically
- **Framework:** Laravel 11 on PHP 8.4
- **Git remote:** SSH to Gitea via deploy key (see `DEPLOY.md`)
- **Branch deployed:** `main`
## Database — SQLite
- **File:** `/var/www/dvla-api/database/database.sqlite`
- **Perms:** file `660`, directory `770`, both owned `deploy:www-data`
- Directory must be writable by `www-data` so SQLite can create
`-journal` / `-wal` / `-shm` siblings
- **Journal mode:** WAL (enabled with
`sqlite3 database/database.sqlite "PRAGMA journal_mode=WAL;"`)
- **Backups:** _<TODO: document backup mechanism — see Backups section>_
`SESSION_DRIVER`, `CACHE_STORE`, and `QUEUE_CONNECTION` are all `database`,
meaning sessions/cache/queue tables live in the same SQLite file as app
data. Acceptable for low traffic; revisit if write contention shows up
(`database is locked` errors in `storage/logs/laravel.log`).
## Queue / scheduler
- **Worker:** _<TODO: either set `QUEUE_CONNECTION=sync` in `.env` or
install the systemd unit below>_
- **Scheduler:** _<TODO: cron entry for `php artisan schedule:run` if any
scheduled tasks exist>_
## Network / firewall
- **UFW:** _<TODO: confirm enabled; document allowed ports>_
- **Open ports (verified via `ss -tlnp`):**
- 22 — SSH
- 80 — HTTP (nginx)
- 53 — systemd-resolved, loopback only (not public)
- **TLS:** _<TODO: not yet configured. Plan: domain → Let's Encrypt via certbot>_
## Backups
_<TODO. SQLite makes this easy: a single file. Suggested approach:
nightly cron on `deploy` running `sqlite3 database/database.sqlite ".backup
/var/backups/dvla-api/db-$(date +\%F).sqlite"` followed by offsite copy
(rsync to home server / S3 / Hetzner Storage Box).>_
## Known divergences from the multi-app pattern in project chats
This box was set up for a single app and intentionally simplified:
- Single `deploy` user instead of `deploy-<appname>`
- Default `www-data` PHP-FPM pool instead of a per-app pool
- No Redis (cache/sessions/queue all on SQLite)
These are acceptable as long as this box hosts one app. Revisit the older
project chats before adding a second app to this VPS.