Deploy on a PaaS
A PaaS builds the repository’s Dockerfile, runs the container behind its own TLS proxy and gives it a database. This page is what the image offers any platform, followed by step-by-step instructions for Coolify. For a server of your own with the bundled compose stack, see Docker instead.
What the image offers
Section titled “What the image offers”| Fact | Value |
|---|---|
| Build | The Dockerfile in the repository root (two stages, no build arguments). Choose “Dockerfile”, not a buildpack. |
| Port | 4177. The server listens on 0.0.0.0; both are set in the image. |
| Health | GET /health answers 200 without login or database. The image has its own HEALTHCHECK on it, so the platform needs no health-check configuration. |
| Migrations | The entrypoint applies pending migrations before the server starts, retrying while the database comes up. No release command, no manual db:migrate. |
| Storage | PostgreSQL 14 or later (DATABASE_URL). Media on two persistent volumes, /app/server/uploads and /app/server/data, or in S3, in which case neither needs a volume (see below). |
| TLS | Terminated by the platform. The repository’s Caddyfile and the compose caddy service are not used. Set APP_URL to the https:// origin and TRUST_PROXY=true. |
| User | The container runs as node (uid 1000). A mounted volume must be writable by that uid. |
| Environment | npm run setup -- --profile production --out .env.production --app-url https://slides.example.com --admin-email ops@example.com writes the minimal production block, with a generated AUTH_SECRET. Fill in the empty values and paste the result into the platform’s environment settings. |
| Last step | node scripts/doctor.js in the running container: every check green, or a warning you chose, before the first user. See Check the configuration. |
Two things that are not the platform’s job and still go wrong:
DATABASE_HOST=localhostinside a container points at the app itself. The entrypoint refuses it and says so.- A database on the platform’s private network usually speaks plaintext, so set
DATABASE_SSL=falsefor it. SSL is on by default for any host other than localhost.
Volumes
Section titled “Volumes”The image has two directories the server writes, both owned by node. Mount a volume on each path exactly as it is: a volume on a path the image already has takes the image’s owner and is writable as created; a volume on any other path is created root-owned, and the server cannot write it.
| Path | Holds | Volume? |
|---|---|---|
/app/server/uploads | Uploaded images and files, served under /uploads/. | Yes, unless S3. |
/app/server/data | Uploaded font variants, which the app serves itself, and a thumbnail cache that is rebuilt on demand. | Yes, unless S3: without it, uploaded fonts vanish at every redeploy. |
With S3, uploads and font variants both live in the bucket (uploads/* and private/*). Only the thumbnail cache stays on disk, and it needs no volume. See Media services.
Coolify
Section titled “Coolify”- Database. Add a PostgreSQL resource (16 is fine). Copy its internal connection URL; the app reaches it over Coolify’s network by service name.
- Application. Add a resource from the Git repository: the GitHub App for a private fork (it also gives you deploy on push), or a deploy key for a plain SSH clone. Build pack: Dockerfile. Ports exposed:
4177. - Domain. Set the domain on the application (
https://slides.example.com). Coolify’s proxy fetches the certificate and forwards to port 4177. - Environment. Paste the block from
npm run setup -- --profile productioninto the environment variables (Coolify accepts a whole.envat once), withDATABASE_URLset to the internal URL from step 1 andDATABASE_SSL=false. - Storage. Add two persistent volumes with destination paths
/app/server/uploadsand/app/server/data(skip both with S3). A volume Coolify creates takes the image’s owner and is writable as is; if you bind a host directory instead,chown 1000:1000it on the host. - Deploy, wait for the container to turn healthy, then open the application’s Terminal and run
node scripts/doctor.js. - Auto deploy. With the GitHub App, enable automatic deployment on push to the branch you deploy. To control when updates land, deploy a release tag rather than
main.
Back-ups of that database: Back up and restore.
Other platforms
Section titled “Other platforms”The table at the top is all a platform needs; each names the same things in its own words. Railway, Render and Fly each build a Dockerfile, take 4177 as the internal port, and offer a managed PostgreSQL plus persistent volumes or disks to mount at /app/server/uploads and /app/server/data. Where the platform has no volumes, use S3. Whatever the platform, the last step is the same: the doctor in the running container.