Files

8.5 KiB

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
    • Windows entrypoint for packaging the repo, uploading it, and running the remote deployment helper over SSH.
  • scripts/deploy.ps1
    • Deprecated compatibility wrapper that forwards to the repo-root deploy.ps1. Keep using the root script directly.
  • scripts/deploy-remote.sh
    • Server-side helper that updates only the goodwalk-svelte compose project.
  • 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
    • 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
    • Repo-local SSH config used by the deployment script.
  • 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, including the onboarding subdomain.

First-time server preparation

  1. Fill in ssh-config with the real host details.

  2. Create the deployment directory on the server:

mkdir -p /docker/goodwalk-svelte
  1. The first deployment will auto-create the production env file on the server at:
/docker/goodwalk-svelte/.env

It is created from deploy.env.template. Current template contents:

APP_VERSION=4.2.3
ENABLE_GENERAL_ENQUIRIES=false
PUBLIC_ENABLE_MOBILE_CTA_BUTTON=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 <bookings@goodwalk.co.nz>
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

Frontend flags:

  • PUBLIC_ENABLE_MOBILE_CTA_BUTTON=false keeps the sticky mobile booking CTA hidden.
  • Set PUBLIC_ENABLE_MOBILE_CTA_BUTTON=true to show it again.
  1. Confirm the shared Docker network already exists:
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 -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 -ExecutionPolicy Bypass -File .\deploy.ps1 -Force

To rebuild and restart only one service, for example the mail API:

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 on the server.

Recommended credential setup for a private HTTPS repo:

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:

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:

/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:

/usr/local/bin/goodwalk-deploy \
  --repo-url https://g.sublogue.com/admin/gw-svelte.git \
  --branch main \
  --ref <commit-or-tag> \
  --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:

/docker/nginx/conf.d/goodwalk.co.nz.conf

Use the repo example as the new target config:

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.
  • The same nginx config now also routes onboarding.goodwalk.co.nz to the Svelte app and /api/onboarding-submit to the shared mail API.
  • Before cutover, confirm the server has a valid certificate for onboarding.goodwalk.co.nz, or adjust the onboarding certificate paths in the nginx config to match your cert layout.

Manual nginx commands, if you ever need them:

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.