110 lines
5.9 KiB
Markdown
110 lines
5.9 KiB
Markdown
# Production deployment
|
|
|
|
## Coolify on one VM
|
|
|
|
Use the repository Docker Compose build pack and `/compose.coolify.yml`.
|
|
This adds isolated Postgres and Garage volumes, migrations, and a one-shot
|
|
storage initializer (exact-origin CORS, one-day export expiry and abandoned
|
|
multipart cleanup). It does not import local demo data or create accounts.
|
|
Coolify generates `SERVICE_URL_WEB` and `SERVICE_URL_GARAGE`; these are passed
|
|
to the app/auth and browser storage endpoint respectively. Route web to port
|
|
3000 and Garage to 3900. Both generated domains must use HTTPS. Only these
|
|
services are public; do not expose Postgres or Garage's admin/RPC ports.
|
|
Set `EMAIL_FROM`, `RESEND_API_KEY`, and `RESEND_WEBHOOK_SECRET` in Coolify before
|
|
starting. Generated credentials belong to this stack, not other applications.
|
|
Coolify does not generate 24-character hex values automatically: initialize
|
|
`SERVICE_HEX_24_S3KEY` with 12 cryptographically random bytes encoded as hex
|
|
before the first deployment. The Compose file adds Garage's `GK` prefix.
|
|
Keep that identifier and all storage secrets stable across redeployments.
|
|
Keep worker concurrency at one initially on a shared VM. Back up all three
|
|
data volumes off-host and verify recovery before collecting real wedding photos.
|
|
|
|
## Standalone Compose
|
|
|
|
Requires Docker Compose, an HTTPS reverse proxy, and a private S3-compatible
|
|
bucket. Compose includes persistent Postgres, a one-shot migration service,
|
|
the standalone web app, and a persistent Bun image/email/export worker.
|
|
Mailpit and demo credentials are not included. Production never auto-creates
|
|
the bucket or changes its CORS configuration.
|
|
|
|
1. Copy `.env.production.example` to `.env.production` and fill every required
|
|
secret. Use random values, not development credentials. Keep auth and public
|
|
URLs identical to the final HTTPS origin. The public URL is also a build arg;
|
|
rebuild the web image when changing it.
|
|
2. Provision the private bucket and configure CORS: allow your exact public app
|
|
origin, GET/PUT/HEAD, Content-Type and necessary S3 checksum headers; expose
|
|
ETag and Content-Length. Guests must reach `S3_PUBLIC_ENDPOINT` over HTTPS.
|
|
The worker needs list/get/put/delete and multipart-upload permissions.
|
|
Set a one-day lifecycle expiry for the `exports/` prefix and abort incomplete
|
|
multipart uploads after one day. Do not expire originals. The worker also
|
|
removes expired ZIPs, but lifecycle handles archives belonging to deleted events.
|
|
3. Verify the Resend sender domain. Configure `/api/webhooks/resend` as described
|
|
in `email-delivery.md`. Set Authentik's callback if using it (see README).
|
|
4. Run:
|
|
|
|
```sh
|
|
docker compose --env-file .env.production -f compose.production.yml build
|
|
docker compose --env-file .env.production -f compose.production.yml up -d
|
|
docker compose --env-file .env.production -f compose.production.yml ps
|
|
```
|
|
|
|
The web service binds only `127.0.0.1:3000`. Configure your host reverse proxy to
|
|
forward HTTPS to that address, preserve Host and X-Forwarded-Proto, and support
|
|
normal streaming responses. Postgres is not published to the host. If your proxy
|
|
runs in Docker, attach it to the Compose network and use `web:3000` instead.
|
|
|
|
## First administrator
|
|
|
|
Register your real account in the app and verify its email (or sign in through
|
|
Authentik). Obtain its ID from your database administrator, then run:
|
|
|
|
```sh
|
|
docker compose --env-file .env.production -f compose.production.yml exec worker bun packages/database/src/bootstrap-admin.ts USER_ID
|
|
```
|
|
|
|
This promotes only an existing verified account and refuses once any platform
|
|
administrator exists. No passwords are created or logged. Never run demo seeds.
|
|
|
|
## Updates and operations
|
|
|
|
Back up Postgres and the original-image bucket before updates. Build updated
|
|
images, then run `docker compose ... run --rm migrate` before `up -d` (replace
|
|
`...` with the same env-file/file arguments above). Do not use `down -v` on a
|
|
live installation. Schema rollback requires a tested backup/restore strategy.
|
|
Monitor web health at `/api/health` (process liveness only), worker logs, failed
|
|
jobs, disk usage, database backups and storage lifecycle rules. Test restores.
|
|
Pin image digests in your deployment pipeline after verifying them.
|
|
|
|
## Originals exports
|
|
|
|
Event → Photos → Export originals. Owners/managers with settings and private-photo
|
|
access can choose approved or all processed photos. Exports are requester-only;
|
|
permissions are rechecked before issuing each five-minute signed download URL.
|
|
Already-issued URLs remain usable until expiry. ZIP files contain unchanged
|
|
original bytes (including EXIF/GPS), named by photo ID to prevent unsafe or
|
|
duplicate filenames. Standalone banners and unprocessed uploads are excluded.
|
|
The selection is taken when processing starts. No originals pass through Next.js.
|
|
ZIP64, streaming multipart uploads and one-at-a-time input reads bound memory.
|
|
One active export per requester/event is allowed; limit 10,000 photos / 20 GB.
|
|
Archives expire after 24 hours; interrupted jobs fail and may be requested again.
|
|
|
|
## Verification
|
|
|
|
Validated locally: web/worker/migration Docker image builds (Linux ARM64), web
|
|
container startup and health response, worker native Sharp and ZIP dependencies,
|
|
Compose configuration, clean database migrations, typecheck and application build.
|
|
The demo's eight exported originals were compared byte-for-byte successfully.
|
|
No production services were deployed and no production emails were sent.
|
|
|
|
```sh
|
|
EXPORT_INTEGRATION=1 bun --env-file=.env test apps/worker/src/exports.integration.test.ts
|
|
cd apps/web
|
|
EXPORT_PERMISSIONS_INTEGRATION=1 bun --env-file=../../.env test src/server/exports.integration.test.ts
|
|
```
|
|
|
|
The local-only integration tests cover archive contents, original bytes,
|
|
approved/private filtering, expiry deletion, duplicate jobs, event/requester
|
|
scoping, denied roles, revoked permissions, and expired download rejection.
|
|
They remove their own temporary fixtures. Validate your actual S3 provider and
|
|
target architecture in staging before launch.
|