# Deployment ## Server layout confirmed The production server currently runs multiple separate Docker Compose projects: - Main public site WordPress stack: - project: `goodwalkconz` - path: `/docker/wordpress/goodwalk.co.nz` - Onboarding WordPress stack: - project: `onboardinggoodwalkconz` - path: `/docker/wordpress/onboarding.goodwalk.co.nz` - Shared nginx: - project: `nginx` - path: `/docker/nginx` - Shared mysql: - project: `mysql` - path: `/docker/mysql` The deployment scripts in this repo are set up to deploy the new Svelte site as a separate stack at: - remote path: `/docker/goodwalk-svelte` - compose file: `docker-compose.prod.yml` - docker project: `goodwalk-svelte` This leaves the onboarding site, shared nginx, shared mysql, and other unrelated containers untouched. ## Files involved - [deploy.ps1](deploy.ps1) - Windows entrypoint for packaging the repo, uploading it, and running the remote deployment helper over SSH. - [scripts/deploy.ps1](scripts/deploy.ps1) - Deprecated compatibility wrapper that forwards to the repo-root `deploy.ps1`. Keep using the root script directly. - [scripts/deploy-remote.sh](scripts/deploy-remote.sh) - Server-side helper that updates only the `goodwalk-svelte` compose project. - [scripts/deploy-from-git.sh](scripts/deploy-from-git.sh) - Standalone server-side entrypoint that pulls from Git, then runs the same compose/nginx deployment steps on the server. - [docker-compose.prod.yml](docker-compose.prod.yml) - Production compose file for the new Svelte app, mail API, and Postgres. - `scripts/export-homepage-content.mjs` - Local helper that exports the current `src/lib/content/homepage.ts` into a deployable JSON payload before each deployment. - `scripts/sync-homepage-content.mjs` - Runtime helper that upserts the exported homepage content into PostgreSQL after deploys that affect the app/database. - [ssh-config](ssh-config) - Repo-local SSH config used by the deployment script. - [nginx/goodwalk.co.nz.svelte.conf.example](nginx/goodwalk.co.nz.svelte.conf.example) - Example shared-nginx config for routing the main public site to the new Svelte app and mail API. ## First-time server preparation 1. Fill in [ssh-config](ssh-config) with the real host details. 2. Create the deployment directory on the server: ```bash mkdir -p /docker/goodwalk-svelte ``` 3. The first deployment will auto-create the production env file on the server at: ```bash /docker/goodwalk-svelte/.env ``` It is created from [deploy.env.template](deploy.env.template). Current template contents: ```env APP_VERSION=4.2.2 ENABLE_GENERAL_ENQUIRIES=false TZ=Pacific/Auckland POSTGRES_DB=goodwalk POSTGRES_USER=goodwalk POSTGRES_PASSWORD=gw_Pg_7Jm9!Qx4#Ld2@Vr8 POSTGRES_PASSWORD_URLENCODED=gw_Pg_7Jm9%21Qx4%23Ld2%40Vr8 RESEND_API_KEY=replace-me OWNER_EMAIL=replace-me FROM_EMAIL=GoodWalk REPLY_TO=aless@goodwalk.co.nz FORM_MIN_SECONDS=4 FORM_MAX_SECONDS=7200 RATE_LIMIT_WINDOW_SECONDS=900 RATE_LIMIT_MAX_PER_IP=5 RATE_LIMIT_MAX_PER_EMAIL=3 RATE_LIMIT_MIN_INTERVAL_SECONDS=20 ``` After the first deploy, edit `/docker/goodwalk-svelte/.env` on the server and replace: - `RESEND_API_KEY=replace-me` - `OWNER_EMAIL=replace-me` 4. Confirm the shared Docker network already exists: ```bash docker network ls | grep webnet ``` Your server already uses `webnet`, so this should already be present. ## First deploy From Windows PowerShell in the repo root: ```powershell powershell -ExecutionPolicy Bypass -File .\deploy.ps1 ``` This is the single supported deployment entrypoint. If you see `scripts/deploy.ps1`, that file now just forwards to the root script so the deployment logic only lives in one place. Or skip the confirmation prompt: ```powershell powershell -ExecutionPolicy Bypass -File .\deploy.ps1 -Force ``` To rebuild and restart only one service, for example the mail API: ```powershell powershell -ExecutionPolicy Bypass -File .\deploy.ps1 -Force -Service mail-api ``` ## Remote Git deploy If you want the production server to pull straight from Gitea instead of receiving an uploaded tarball from Windows, use [scripts/deploy-from-git.sh](scripts/deploy-from-git.sh) on the server. Recommended credential setup for a private HTTPS repo: ```bash umask 077 cat > ~/.netrc <<'EOF' machine g.sublogue.com login YOUR_GITEA_USERNAME password YOUR_READ_ONLY_TOKEN EOF chmod 600 ~/.netrc ``` Install the script on the server and make it executable: ```bash install -m 0755 scripts/deploy-from-git.sh /usr/local/bin/goodwalk-deploy ``` The remote host must have `git` and `docker`. A host-level `node` install is optional; if it is missing, the script will export homepage content using a temporary `node:22-alpine` container instead. Run a full deploy from the repo: ```bash /usr/local/bin/goodwalk-deploy \ --repo-url https://g.sublogue.com/admin/gw-svelte.git \ --branch main \ --deploy-path /docker/goodwalk-svelte \ --compose-file docker-compose.prod.yml \ --project-name goodwalk-svelte \ --nginx-source nginx/goodwalk.co.nz.svelte.conf.example \ --nginx-target /docker/nginx/conf.d/goodwalk.co.nz.conf \ --nginx-compose-file /docker/nginx/docker-compose.yml \ --nginx-project-name nginx \ --maintenance-host-dir /docker/nginx/maintenance \ --maintenance-flag /docker/nginx/conf.d/maintenance.flag ``` Deploy a specific commit or tag: ```bash /usr/local/bin/goodwalk-deploy \ --repo-url https://g.sublogue.com/admin/gw-svelte.git \ --branch main \ --ref \ --deploy-path /docker/goodwalk-svelte \ --compose-file docker-compose.prod.yml \ --project-name goodwalk-svelte \ --nginx-source nginx/goodwalk.co.nz.svelte.conf.example \ --nginx-target /docker/nginx/conf.d/goodwalk.co.nz.conf \ --nginx-compose-file /docker/nginx/docker-compose.yml \ --nginx-project-name nginx \ --maintenance-host-dir /docker/nginx/maintenance \ --maintenance-flag /docker/nginx/conf.d/maintenance.flag ``` ## Homepage content sync Local development can feel fresher than production because production reads the homepage/shared content from PostgreSQL whenever `DATABASE_URL` is set. The deployment flow now handles that automatically: 1. `deploy.ps1` exports the current `src/lib/content/homepage.ts` into `deploy-data/homepage-content.json`. 2. The deploy archive uploads that JSON payload with the app source. 3. After the Goodwalk stack is updated, the remote helper runs a content sync inside the app container. 4. That sync upserts the `homepage` row in `site_content`. This means future deploys will carry your latest file-based homepage/navigation/ shared content changes into production PostgreSQL automatically. ## Cutover nginx After the new Svelte stack is up and healthy, update the shared nginx config on the server for the main site. Current live file: ```bash /docker/nginx/conf.d/goodwalk.co.nz.conf ``` Use the repo example as the new target config: ```bash nginx/goodwalk.co.nz.svelte.conf.example ``` Important: - `deploy.ps1` now copies the repo nginx config to `/docker/nginx/conf.d/goodwalk.co.nz.conf` and reloads the shared nginx container as part of deployment. - The repo nginx config uses Docker's internal resolver so future app/mail container rebuilds will not leave nginx pinned to stale upstream IPs. Manual nginx commands, if you ever need them: ```bash docker compose -p nginx -f /docker/nginx/docker-compose.yml exec nginx nginx -t docker compose -p nginx -f /docker/nginx/docker-compose.yml exec nginx nginx -s reload ``` ## Important notes - Do not deploy the top-level `docker-compose.yml` to this server for production. It includes its own nginx service and does not match the shared nginx setup on the host. - The deployment scripts do not stop or remove the onboarding WordPress stack. - The deployment scripts do not touch the shared mysql compose project. - The deployment scripts preserve the remote `.env` file. - The site check in `deploy.ps1` targets `https://www.goodwalk.co.nz/api/health`. Before nginx cutover, use `-SkipSiteCheck` or expect that check to fail.