Skip to content

CI / CD

Status

Workflow written, not yet wired. The pipeline exists at .github/workflows/deploy.yml; it activates once the repo is pushed to GitHub. Today we deploy locally by running docker build + docker restart fruitplug-web-dev.

Target pipeline

.github/workflows/deploy.yml on every push to main:

flowchart LR
  Push([git push main]) --> Build[docker buildx<br/>apps/web/Dockerfile]
  Build --> GHCR[ghcr.io/&lt;org&gt;/<br/>fruitplug-web:sha<br/>+ :latest]
  GHCR --> SSH[SSH to pwa-host]
  SSH --> Pull[docker compose pull<br/>systemctl restart]
  Pull --> Smoke[curl /healthz<br/>30×4s retries]
  Smoke --> Done([deployed])

Required GitHub secrets (when wired)

Secret Purpose
SSH_HOST pwa-host hostname or IP
SSH_USER Deploy user (sudoer or owns /opt/fruitplug)
SSH_PORT Optional; defaults to 22
SSH_PRIVATE_KEY PEM contents of the deploy key
BASIC_AUTH user:password for Caddy basic-auth smoke test (if re-enabled)

GHCR_TOKEN is not needed — the pipeline uses the automatic GITHUB_TOKEN scoped to the repo.

Deploy script on the server

/opt/fruitplug/infra/deploy.sh:

#!/usr/bin/env bash
set -euo pipefail
cd /opt/fruitplug
docker compose -f infra/docker-compose.prod.yml pull
systemctl restart fruitplug-web
for i in {1..30}; do
  if curl -sf http://127.0.0.1:3000/healthz >/dev/null; then
    echo "ok"
    exit 0
  fi
  sleep 2
done
exit 1

Today's deploy flow (manual)

Until CI is wired, we deploy the PWA by editing locally, which:

  1. Hits the dev container's hot-reload loop in seconds (for iteration)
  2. Or: docker build -f apps/web/Dockerfile -t ghcr.io/fruitplug/fruitplug-web:local . + docker restart (for prod-build testing)

For fruitplug-api on A2:

A2_PASSPHRASE=... python infra/a2/rsync_plugin.py

This uploads the latest plugin files via SFTP and activates the plugin. The plugin header's Version should bump on significant changes so WP triggers Migrations::migrate().

Rollback (post-CI)

# On the server
docker pull ghcr.io/fruitplug/fruitplug-web:<previous-sha>
docker tag ghcr.io/fruitplug/fruitplug-web:<previous-sha> ghcr.io/fruitplug/fruitplug-web:latest
systemctl restart fruitplug-web

GHCR retains the previous ~10 tags by default.

Preview environments (future)

When the repo is public/private on GitHub, add a preview.yml workflow that:

  1. Builds per-PR image
  2. Deploys to Fly.io machine with the PR number in the URL (e.g. pr-42.fruitplug.co.uk)
  3. Auto-teardowns on PR close

Not urgent for Phase 1 — nice-to-have for team scaling later.