From c8260cb6e2475d8425b9b421208b57292cb236a3 Mon Sep 17 00:00:00 2001 From: f-amine Date: Thu, 18 Jun 2026 15:32:30 +0100 Subject: [PATCH] fix(deploy): migrate stays healthy instead of exiting (Dokploy) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The migrate service was a one-shot that exited 0 after applying migrations. Dokploy's Compose orchestration treats an exited init container as the deploy state and doesn't reliably start services that depend on it via `service_completed_successfully` — so web/admin never came up and the only logs/status were migrate's "exited". Now migrate applies migrations, writes /tmp/migrated, then idles (`tail -f /dev/null`) so it stays a running, healthy container. web/admin depend on its healthcheck (`test -f /tmp/migrated`) instead of on completion. Nothing exits; dependents start once migrations are done. On migration failure the `&&` chain aborts and `restart: unless-stopped` retries. Verified locally: built the migrate image, ran it against Postgres — migrations applied, marker written, container stays Up, schema created. Co-Authored-By: Claude Opus 4.8 (1M context) --- docker-compose.prod.yml | 18 ++++++++++++------ docker/Dockerfile.migrate | 16 ++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 994f5d5..98cc64c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -49,20 +49,26 @@ services: timeout: 5s retries: 5 - # One-shot: applies committed Drizzle migrations (packages/db/src/migrations) - # before web/admin start. Generate migrations with `pnpm db:generate` and - # commit them — this service is a no-op if none exist yet. + # Applies committed Drizzle migrations (packages/db/src/migrations), then + # idles so it stays a healthy long-running container. web/admin wait on its + # healthcheck. Generate migrations with `pnpm db:generate` and commit them. migrate: build: context: . dockerfile: docker/Dockerfile.migrate - restart: "no" + restart: unless-stopped depends_on: postgres: condition: service_healthy env_file: .env networks: - internal + healthcheck: + test: ["CMD-SHELL", "test -f /tmp/migrated"] + interval: 5s + timeout: 3s + retries: 30 + start_period: 5s web: build: @@ -79,7 +85,7 @@ services: redis: condition: service_healthy migrate: - condition: service_completed_successfully + condition: service_healthy env_file: .env # No host port: Traefik (Dokploy) routes to the container over # dokploy-network using the port set in the Domain config. Publishing @@ -133,7 +139,7 @@ services: redis: condition: service_healthy migrate: - condition: service_completed_successfully + condition: service_healthy env_file: .env expose: - "3002" diff --git a/docker/Dockerfile.migrate b/docker/Dockerfile.migrate index 3e679d1..7f0de98 100644 --- a/docker/Dockerfile.migrate +++ b/docker/Dockerfile.migrate @@ -1,9 +1,12 @@ # syntax=docker.io/docker/dockerfile:1 -# One-shot migration runner for docker-compose.prod.yml. -# Installs only @vibestack/db and its workspace deps, then runs -# `drizzle-kit migrate` (applies committed SQL from packages/db/src/migrations). -# Generate migrations locally with `pnpm db:generate` and commit them — -# `migrate` is a no-op if the folder is empty. +# Migration runner for docker-compose.prod.yml. +# Installs only @vibestack/db and its workspace deps, applies committed SQL +# from packages/db/src/migrations via `drizzle-kit migrate`, writes a marker, +# then idles so the container stays running (and healthy) instead of exiting. +# Some orchestrators (Dokploy) treat an exited one-shot container as a failed +# deploy and never start the services that depend on it; here dependents wait +# on this container's healthcheck (the marker file), not on it completing. +# Generate migrations locally with `pnpm db:generate` and commit them. FROM node:22-alpine @@ -21,4 +24,5 @@ COPY packages/config packages/config RUN pnpm --filter @vibestack/db... install # DATABASE_URL comes from env_file at runtime (docker-compose.prod.yml). -CMD ["pnpm", "--filter", "@vibestack/db", "run", "db:migrate"] +# Migrate, mark done for the healthcheck, then idle so the container stays up. +CMD ["sh", "-c", "pnpm --filter @vibestack/db run db:migrate && touch /tmp/migrated && echo '[migrate] applied; idling' && tail -f /dev/null"]