From 38277b29eced2efe060d27fec71c6d10c46fec3b Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Wed, 9 Sep 2026 15:38:31 -0400 Subject: [PATCH] feat: add deployment health and backups --- compose.production.yaml | 13 ++++++++++++- docs/DEPLOYMENT.md | 36 ++++++++++++++++++++++++++++++++++++ scripts/backup-postgres.sh | 17 +++++++++++++++++ scripts/restore-postgres.sh | 16 ++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100755 scripts/backup-postgres.sh create mode 100755 scripts/restore-postgres.sh diff --git a/compose.production.yaml b/compose.production.yaml index 55e1b49..21e561e 100644 --- a/compose.production.yaml +++ b/compose.production.yaml @@ -43,6 +43,11 @@ services: condition: service_healthy expose: - "3000" + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then((response) => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))"] + interval: 10s + timeout: 5s + retries: 5 worker: build: @@ -66,9 +71,15 @@ services: dockerfile: Dockerfile.web restart: unless-stopped depends_on: - - api + api: + condition: service_healthy ports: - "${WEB_PORT:-8080}:80" + healthcheck: + test: ["CMD", "wget", "--spider", "--quiet", "http://127.0.0.1/health"] + interval: 10s + timeout: 5s + retries: 5 volumes: postgres-data: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index a8d12e4..cce543d 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -45,6 +45,42 @@ worker images share the same Node workspace image; the web image builds the Vue application and serves it through Nginx with SPA fallback and internal `/api` proxying. +## Health Checks + +Production Compose waits for healthy PostgreSQL, Redis, and API services before +starting the web service. The API health endpoint is `GET /health`; the web +container checks its `/health` response through Nginx. + +## Backup And Restore + +Create a PostgreSQL custom-format dump from the running production stack: + +```sh +BACKUP_DIR=/secure/backups \ +POSTGRES_DB=inventory \ +POSTGRES_USER=inventory \ +POSTGRES_PASSWORD='use-secret-storage' \ +./scripts/backup-postgres.sh +``` + +Restore only during a planned maintenance window because `pg_restore` replaces +matching database objects: + +```sh +BACKUP_FILE=/secure/backups/inventory-20260909T000000Z.dump \ +POSTGRES_DB=inventory \ +POSTGRES_USER=inventory \ +POSTGRES_PASSWORD='use-secret-storage' \ +./scripts/restore-postgres.sh +``` + +Back up image storage and the deployment `.env` through the NAS secret/backup +system as well. Never commit either one. Before upgrading, record the current +image tags and database migration state. To roll back, stop the application +services, restore the previous image tags, restore the database dump if the +schema changed, then start the stack and verify `/health` and a read-only +catalog request. + ## Reverse proxy Responsible for: diff --git a/scripts/backup-postgres.sh b/scripts/backup-postgres.sh new file mode 100755 index 0000000..07625ab --- /dev/null +++ b/scripts/backup-postgres.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -eu + +: "${BACKUP_DIR:?Set BACKUP_DIR to the destination directory}" +: "${POSTGRES_DB:?Set POSTGRES_DB}" +: "${POSTGRES_USER:?Set POSTGRES_USER}" +: "${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}" + +timestamp=$(date -u '+%Y%m%dT%H%M%SZ') +mkdir -p "$BACKUP_DIR" +output_path="$BACKUP_DIR/${POSTGRES_DB}-${timestamp}.dump" + +docker compose -f compose.production.yaml exec -T \ + -e PGPASSWORD="$POSTGRES_PASSWORD" postgres \ + pg_dump -U "$POSTGRES_USER" -Fc "$POSTGRES_DB" > "$output_path" + +printf '%s\n' "$output_path" \ No newline at end of file diff --git a/scripts/restore-postgres.sh b/scripts/restore-postgres.sh new file mode 100755 index 0000000..5dc708e --- /dev/null +++ b/scripts/restore-postgres.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu + +: "${BACKUP_FILE:?Set BACKUP_FILE to a PostgreSQL custom-format dump}" +: "${POSTGRES_DB:?Set POSTGRES_DB}" +: "${POSTGRES_USER:?Set POSTGRES_USER}" +: "${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}" + +if [ ! -f "$BACKUP_FILE" ]; then + printf '%s\n' "Backup file not found: $BACKUP_FILE" >&2 + exit 1 +fi + +docker compose -f compose.production.yaml exec -T \ + -e PGPASSWORD="$POSTGRES_PASSWORD" postgres \ + pg_restore --clean --if-exists --no-owner -U "$POSTGRES_USER" -d "$POSTGRES_DB" < "$BACKUP_FILE" \ No newline at end of file