Skip to content

Operations on Coolify

The production platform is intentionally understandable by one maintainer: one Git-backed Docker Compose application, one managed PostgreSQL resource, private Redis, health checks, and a scheduled prompt-publishing task.

Container roles

ServicePublic?StateHealth meaning
verifyNoNone; exitsAll static checks and builds passed
migrateNoSchema changes; exitsEvery committed Prisma migration applied
webYes, 3000PostgreSQLHTTP process is ready
adminYes, 3001PostgreSQLAdmin HTTP process is ready
docsYes, 3003Static filesLearning site can serve generated output
redisNoBounded AOF volumeAuthenticated Redis responds
workerNoRedis transportWorker accepts jobs and Redis is ready
deploy-watcherNoNonePolls main and triggers the next release

Why Compose health dependencies matter

Starting a process is not the same as making a release ready. Compose uses explicit conditions:

yaml
web:
  depends_on:
    migrate:
      condition: service_completed_successfully

admin:
  depends_on:
    web:
      condition: service_healthy

docs:
  depends_on:
    verify:
      condition: service_completed_successfully

If verification or migration fails, new database-dependent application containers do not replace the healthy release with an incompatible version. Static docs still avoid a false database dependency: they wait for verification, not migration.

Static docs deployment

VitePress generates HTML, CSS, JavaScript, a local search index, and a sitemap during the release build. The runtime image contains only Node and those generated files. It has no production environment secrets, database client, or application source.

dockerfile
FROM node:24-bookworm-slim AS docs
WORKDIR /app
COPY --from=release-build /app/apps/docs/server.mjs ./server.mjs
COPY --from=release-build /app/apps/docs/.vitepress/dist ./site
USER node
CMD ["node", "server.mjs"]

The small server handles clean URLs, cache headers, security headers, and /health. Content-hashed assets cache for a year; HTML always revalidates so a new lesson appears immediately after deployment.

Scheduled prompt publishing

Coolify runs one daily task at 0 12 * * * UTC inside the admin container:

bash
node /app/scripts/run-publish-due-prompts.mjs

The script calls the protected admin endpoint using CRON_SECRET and exits non-zero on failure. Scheduling changes lifecycle state; it does not fan out an assignment to every couple. A couple's stable daily assignment is created idempotently on read.

Push-to-deploy without public control-plane access

The Coolify control plane is tailnet-only. A private deploy-watcher polls the repository's main ref using a read-only deploy key. When the commit changes, it signs a minimal GitHub push payload and submits it to Coolify's private manual-webhook endpoint.

This avoids exposing the dashboard or relying on a paid external runner. The tradeoff is a small polling process whose credential and webhook health must be checked when deployments stop moving.

Production diagnostics

Start with the cheapest discriminators:

bash
curl --fail https://www.everduet.com/api/health
curl --fail https://admin.everduet.com/api/health
curl --fail https://docs.everduet.com/health

Then inspect the latest deployment, container health, and structured logs in Coolify. Use x-request-id to correlate a request without copying private content.

Migration posture

Migrations are forward-only in production:

  1. commit a Prisma migration with the code;
  2. let the release gate apply it before new services start;
  3. make additive changes when a staged rollout needs old and new code compatibility;
  4. correct a bad deployed schema with a new migration.

Editing an already applied migration destroys the audit trail and makes environments disagree about what “applied” means.

What this platform deliberately does not add

  • Kubernetes or a service mesh;
  • independent service release trains;
  • a second database authority;
  • paid monitoring as a release requirement;
  • automatic host pruning that can destroy useful state;
  • queue-driven product work without a measured reason.

The platform can grow when a real constraint appears. Until then, simplicity is an operational feature.

Next: tradeoff explorer.

Built as a living architecture notebook for a personal project.