From 393b841023aa6e556bd86f47db300801bac871fe Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 3 Aug 2026 11:25:55 +0800 Subject: [PATCH] =?UTF-8?q?=E9=83=A8=E7=BD=B2docker=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 22 ++++++++++ DOCKER_DEPLOYMENT.md | 75 ++++++++++++++++++++++++++++++++++ Dockerfile.frontend | 10 +++++ backend/.dockerignore | 10 +++++ backend/Dockerfile | 32 +++++++++++++++ deploy/.env.production.example | 26 ++++++++++++ deploy/frontend-entrypoint.sh | 34 +++++++++++++++ deploy/nginx.conf | 35 ++++++++++++++++ docker-compose.yml | 40 ++++++++++++++++++ 9 files changed, 284 insertions(+) create mode 100644 .dockerignore create mode 100644 DOCKER_DEPLOYMENT.md create mode 100644 Dockerfile.frontend create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 deploy/.env.production.example create mode 100644 deploy/frontend-entrypoint.sh create mode 100644 deploy/nginx.conf create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b3f734d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,22 @@ +.git +.idea +.DS_Store +.env +.env.* +*.env +.planning +.work +.tmp +.validation +.codex-tmp +outputs +runtime +private-fixtures +node_modules +backend +*.log +*.sqlite3 +*.sqlite3-* +*.xml +*.XML +*.xlsx diff --git a/DOCKER_DEPLOYMENT.md b/DOCKER_DEPLOYMENT.md new file mode 100644 index 0000000..eb1ceb2 --- /dev/null +++ b/DOCKER_DEPLOYMENT.md @@ -0,0 +1,75 @@ +# Docker Deployment + +This deployment runs the Condo frontend and backend in Docker while connecting +to an existing PostgreSQL database. PostgreSQL is not included in +`docker-compose.yml`. + +## Runtime Shape + +- `frontend`: nginx serves the static files and proxies `/api/*` to the backend. +- `backend`: Node 24 runs the Fastify API on `0.0.0.0:3000`. +- Existing PostgreSQL: must expose the `booking_test` database to the backend + container. + +The browser only needs the frontend URL. API requests go to the same origin +under `/api`, so the backend port does not need to be published. + +## Environment + +Use the variables shown in `deploy/.env.production.example`. The important +settings are: + +- `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME=booking_test` +- `AUTH_USERNAME`, `AUTH_PASSWORD` +- `AUTH_COOKIE_SECURE=false` for HTTP test machines +- `AUTH_COOKIE_SECURE=true` when the public frontend URL uses HTTPS +- `FRONTEND_PORT`, default `8080` + +`DB_HOST` must be resolvable from inside the backend container. If PostgreSQL +runs directly on the Docker host, use the host address that works for that +server environment. + +## Build And Run + +```bash +docker compose build +docker compose up -d +``` + +Open the frontend at: + +```text +http://:8080 +``` + +## Database Migration And Checks + +The database scripts intentionally read one JSON connection line from standard +input. Example shape: + +```json +{"host":"DB_HOST","port":5432,"user":"DB_USER","password":"DB_PASSWORD","database":"booking_test","ssl":false} +``` + +Run migration safety checks without database access: + +```bash +docker compose run --rm backend npm run db:check-migrations +``` + +Run inventory and migrations against the existing PostgreSQL connection: + +```bash +printf '%s\n' "$CONDON_DB_CONNECTION_JSON" | docker compose run --rm -T backend npm run db:inventory +printf '%s\n' "$CONDON_DB_CONNECTION_JSON" | docker compose run --rm -T backend npm run db:migrate:up +``` + +For an already imported database that matches the retained production baseline, +run: + +```bash +printf '%s\n' "$CONDON_DB_CONNECTION_JSON" | docker compose run --rm -T backend npm run db:verify +``` + +`/api/auth/session` is public and works as a basic process check. `/api/health` +requires a valid login cookie. diff --git a/Dockerfile.frontend b/Dockerfile.frontend new file mode 100644 index 0000000..f1d9d5a --- /dev/null +++ b/Dockerfile.frontend @@ -0,0 +1,10 @@ +FROM nginx:1.27-alpine + +COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf +COPY deploy/frontend-entrypoint.sh /docker-entrypoint.d/50-condo-runtime-config.sh + +COPY index.html styles.css app.js api-client.js owners-data.js favicon.svg runtime-config.js /usr/share/nginx/html/ + +RUN chmod +x /docker-entrypoint.d/50-condo-runtime-config.sh + +EXPOSE 80 diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..f30f2b7 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,10 @@ +.env +.env.* +*.env +*.log +node_modules +dist +coverage +.DS_Store +.idea +__pycache__ diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..a0b1a31 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,32 @@ +# syntax=docker/dockerfile:1.7 + +FROM node:24-alpine AS dependencies +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +FROM dependencies AS build +COPY tsconfig.json ./ +COPY src ./src +RUN npm run build + +FROM node:24-alpine AS runtime +WORKDIR /app + +ENV NODE_ENV=production \ + API_HOST=0.0.0.0 \ + API_PORT=3000 + +COPY package*.json ./ +RUN npm ci --omit=dev && npm cache clean --force + +COPY --from=build /app/dist ./dist +COPY migrations ./migrations +COPY scripts ./scripts + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD node -e "fetch('http://127.0.0.1:' + (process.env.API_PORT || 3000) + '/auth/session').then(response => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))" + +CMD ["node", "dist/src/server.js"] diff --git a/deploy/.env.production.example b/deploy/.env.production.example new file mode 100644 index 0000000..7659910 --- /dev/null +++ b/deploy/.env.production.example @@ -0,0 +1,26 @@ +# Frontend +FRONTEND_PORT=8080 +CONDO_API_BASE_URL=/api +CONDO_DEFAULT_MODE=api +CONDO_PERIOD_YEAR= + +# Backend +NODE_ENV=production +LOG_LEVEL=info +CORS_ORIGINS=http://localhost:8080 +AUTH_USERNAME=wyndhamcondon +AUTH_PASSWORD=replace-with-test-password +AUTH_SESSION_TTL_HOURS=12 + +# Set true only when the public frontend is served over HTTPS. +AUTH_COOKIE_SECURE=false + +# Existing PostgreSQL. DB_HOST must be reachable from inside the backend container. +DB_HOST=replace-with-existing-pg-host +DB_PORT=5432 +DB_USER=replace-with-existing-pg-user +DB_PASSWORD=replace-with-existing-pg-password +DB_NAME=booking_test +DB_SSL=false +DB_POOL_MAX=10 +DB_STATEMENT_TIMEOUT_MS=15000 diff --git a/deploy/frontend-entrypoint.sh b/deploy/frontend-entrypoint.sh new file mode 100644 index 0000000..4edfedf --- /dev/null +++ b/deploy/frontend-entrypoint.sh @@ -0,0 +1,34 @@ +#!/bin/sh +set -eu + +runtime_config_path="/usr/share/nginx/html/runtime-config.js" +default_mode="${CONDO_DEFAULT_MODE:-api}" +api_base_url="${CONDO_API_BASE_URL:-/api}" +period_year="${CONDO_PERIOD_YEAR:-}" + +escape_js_string() { + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' +} + +case "$period_year" in + "" ) + period_year_expression="new Date().getUTCFullYear()" + ;; + *[!0-9]* ) + period_year_expression="new Date().getUTCFullYear()" + ;; + * ) + period_year_expression="$period_year" + ;; +esac + +default_mode_escaped="$(escape_js_string "$default_mode")" +api_base_url_escaped="$(escape_js_string "$api_base_url")" + +cat > "$runtime_config_path" <