fix: add DB wait-for-ready logic and explicit network names
Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 9m24s
Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 9m24s
- start-with-migrate.js now waits for the database TCP port to be reachable before running Prisma migrations (15 retries, 2s interval). Prevents the container from crashing and restarting in a loop when postgres is still starting up. - Add explicit 'name:' to both production and dev compose networks to prevent docker-compose project prefix mismatch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,7 @@ volumes:
|
|||||||
|
|
||||||
networks:
|
networks:
|
||||||
portfolio_net:
|
portfolio_net:
|
||||||
|
name: portfolio_net
|
||||||
driver: bridge
|
driver: bridge
|
||||||
proxy:
|
proxy:
|
||||||
external: true
|
external: true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
/**
|
/**
|
||||||
* Container entrypoint: apply Prisma migrations, then start Next server.
|
* Container entrypoint: wait for DB, apply Prisma migrations, then start Next server.
|
||||||
*
|
*
|
||||||
* Why:
|
* Why:
|
||||||
* - In real deployments you want schema changes applied automatically per deploy.
|
* - In real deployments you want schema changes applied automatically per deploy.
|
||||||
@@ -8,8 +8,11 @@
|
|||||||
*
|
*
|
||||||
* Controls:
|
* Controls:
|
||||||
* - Set `SKIP_PRISMA_MIGRATE=true` to skip migrations (emergency / debugging).
|
* - Set `SKIP_PRISMA_MIGRATE=true` to skip migrations (emergency / debugging).
|
||||||
|
* - DB_WAIT_RETRIES (default 15) and DB_WAIT_INTERVAL_MS (default 2000) control
|
||||||
|
* how long to wait for the database before giving up.
|
||||||
*/
|
*/
|
||||||
const { spawnSync } = require("node:child_process");
|
const { spawnSync } = require("node:child_process");
|
||||||
|
const { createConnection } = require("node:net");
|
||||||
const fs = require("node:fs");
|
const fs = require("node:fs");
|
||||||
const path = require("node:path");
|
const path = require("node:path");
|
||||||
|
|
||||||
@@ -24,45 +27,120 @@ function run(cmd, args, opts = {}) {
|
|||||||
throw res.error;
|
throw res.error;
|
||||||
}
|
}
|
||||||
if (typeof res.status === "number" && res.status !== 0) {
|
if (typeof res.status === "number" && res.status !== 0) {
|
||||||
// propagate exit code
|
|
||||||
process.exit(res.status);
|
process.exit(res.status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const skip = String(process.env.SKIP_PRISMA_MIGRATE || "").toLowerCase() === "true";
|
/**
|
||||||
if (!skip) {
|
* Wait for a TCP port to be reachable.
|
||||||
const autoBaseline =
|
* Parses DATABASE_URL to extract host and port.
|
||||||
String(process.env.PRISMA_AUTO_BASELINE || "").toLowerCase() === "true";
|
*/
|
||||||
|
function waitForDb() {
|
||||||
// Avoid relying on `npx` resolution in minimal runtimes.
|
const dbUrl = process.env.DATABASE_URL;
|
||||||
// We copy `node_modules/prisma` into the runtime image.
|
if (!dbUrl) {
|
||||||
if (autoBaseline) {
|
console.log("[startup] No DATABASE_URL set, skipping DB wait.");
|
||||||
try {
|
return Promise.resolve();
|
||||||
const migrationsDir = path.join(process.cwd(), "prisma", "migrations");
|
|
||||||
const entries = fs
|
|
||||||
.readdirSync(migrationsDir, { withFileTypes: true })
|
|
||||||
.filter((d) => d.isDirectory())
|
|
||||||
.map((d) => d.name);
|
|
||||||
const initMigration = entries.find((n) => n.endsWith("_init"));
|
|
||||||
if (initMigration) {
|
|
||||||
// This is the documented "baseline" flow for existing databases:
|
|
||||||
// mark the initial migration as already applied.
|
|
||||||
run("node", [
|
|
||||||
"node_modules/prisma/build/index.js",
|
|
||||||
"migrate",
|
|
||||||
"resolve",
|
|
||||||
"--applied",
|
|
||||||
initMigration,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} catch (_err) {
|
|
||||||
// If baseline fails we continue to migrate deploy, which will surface the real issue.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
run("node", ["node_modules/prisma/build/index.js", "migrate", "deploy"]);
|
|
||||||
} else {
|
let host, port;
|
||||||
console.log("SKIP_PRISMA_MIGRATE=true -> skipping prisma migrate deploy");
|
try {
|
||||||
|
// postgresql://user:pass@host:port/db?schema=public
|
||||||
|
const match = dbUrl.match(/@([^:/?]+):(\d+)/);
|
||||||
|
if (!match) throw new Error("Could not parse host:port from DATABASE_URL");
|
||||||
|
host = match[1];
|
||||||
|
port = parseInt(match[2], 10);
|
||||||
|
} catch (_err) {
|
||||||
|
console.log("[startup] Could not parse DATABASE_URL, skipping DB wait.");
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxRetries = parseInt(process.env.DB_WAIT_RETRIES || "15", 10);
|
||||||
|
const intervalMs = parseInt(process.env.DB_WAIT_INTERVAL_MS || "2000", 10);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let attempt = 0;
|
||||||
|
|
||||||
|
function tryConnect() {
|
||||||
|
attempt++;
|
||||||
|
const sock = createConnection({ host, port }, () => {
|
||||||
|
sock.destroy();
|
||||||
|
console.log(`[startup] Database at ${host}:${port} is reachable.`);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
sock.setTimeout(1500);
|
||||||
|
sock.on("error", () => {
|
||||||
|
sock.destroy();
|
||||||
|
if (attempt >= maxRetries) {
|
||||||
|
console.error(
|
||||||
|
`[startup] Database at ${host}:${port} not reachable after ${maxRetries} attempts. Proceeding anyway...`
|
||||||
|
);
|
||||||
|
resolve(); // still try migration - prisma will give a clear error
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`[startup] Waiting for database at ${host}:${port}... (${attempt}/${maxRetries})`
|
||||||
|
);
|
||||||
|
setTimeout(tryConnect, intervalMs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sock.on("timeout", () => {
|
||||||
|
sock.destroy();
|
||||||
|
if (attempt >= maxRetries) {
|
||||||
|
console.error(
|
||||||
|
`[startup] Database at ${host}:${port} timed out after ${maxRetries} attempts. Proceeding anyway...`
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`[startup] Waiting for database at ${host}:${port}... (${attempt}/${maxRetries})`
|
||||||
|
);
|
||||||
|
setTimeout(tryConnect, intervalMs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tryConnect();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
run("node", ["server.js"]);
|
async function main() {
|
||||||
|
// 1. Wait for database to be reachable
|
||||||
|
await waitForDb();
|
||||||
|
|
||||||
|
// 2. Run migrations
|
||||||
|
const skip =
|
||||||
|
String(process.env.SKIP_PRISMA_MIGRATE || "").toLowerCase() === "true";
|
||||||
|
if (!skip) {
|
||||||
|
const autoBaseline =
|
||||||
|
String(process.env.PRISMA_AUTO_BASELINE || "").toLowerCase() === "true";
|
||||||
|
|
||||||
|
if (autoBaseline) {
|
||||||
|
try {
|
||||||
|
const migrationsDir = path.join(process.cwd(), "prisma", "migrations");
|
||||||
|
const entries = fs
|
||||||
|
.readdirSync(migrationsDir, { withFileTypes: true })
|
||||||
|
.filter((d) => d.isDirectory())
|
||||||
|
.map((d) => d.name);
|
||||||
|
const initMigration = entries.find((n) => n.endsWith("_init"));
|
||||||
|
if (initMigration) {
|
||||||
|
run("node", [
|
||||||
|
"node_modules/prisma/build/index.js",
|
||||||
|
"migrate",
|
||||||
|
"resolve",
|
||||||
|
"--applied",
|
||||||
|
initMigration,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} catch (_err) {
|
||||||
|
// If baseline fails we continue to migrate deploy, which will surface the real issue.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run("node", ["node_modules/prisma/build/index.js", "migrate", "deploy"]);
|
||||||
|
} else {
|
||||||
|
console.log("SKIP_PRISMA_MIGRATE=true -> skipping prisma migrate deploy");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Start the server
|
||||||
|
run("node", ["server.js"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|||||||
Reference in New Issue
Block a user