# Deploy Workflow
Local code → finished image shipped over SSH → container swapped on the host. No
container registry (see DECISIONS.md D8 for why).
## The two channels to the host
Both go to the same GCP box; they're used for different steps:
- **`podman save … | ssh … podman load`** — ships the image *bytes*. This has to use
raw `ssh` because it's a pipe into the remote Podman CLI.
- **`podman --connection gcp-lab …`** — swaps the running container over the existing
rootless socket, hands-free (no manual SSH). The alias is already configured and set
as default:
`ssh://matt@35.203.147.44:22/run/user/1001/podman/podman.sock`.
> The socket is under **UID 1001** on this host (the original architecture doc's 1000 is
> wrong). Using the alias means you never have to think about the UID.
## Why build locally
The `e2-micro` can't build the Vue multi-stage image reliably (memory). Always build on
the local workstation and ship the finished image. Never build on the host.
## `deploy.sh`
Lives at the repo root and is run from there (so the relative `./backend` and
`./frontend` build contexts resolve). Config block at the top holds host/user/key/alias.
Usage:
```bash
./deploy.sh # full cycle: build both, ship both, swap both containers
VERSION=v2 ./deploy.sh # tag images v2 instead of a timestamp → named rollback point
./deploy.sh --build --ship --swap # explicit equivalent of the default
./deploy.sh --restart-caddy # after editing the Caddyfile to add/modify routes
```
With no arguments it runs `build → ship → swap`. `VERSION` defaults to a timestamp; set
it explicitly to create a named point you can redeploy later.
## Steps the script performs
1. **build** — `podman build` the backend and frontend images locally.
2. **ship** — `podman save
| ssh -i matt@35.203.147.44 'podman load'` for
each image.
3. **swap** — over `--connection gcp-lab`: `rm -f` then `run -d` each container with its
name and port mapping.
4. **restart-caddy** (on demand) — `podman --connection gcp-lab restart caddy-proxy`,
needed only when the Caddyfile changed (e.g. new XSS/traversal routes).
## IMPORTANT: keep `swap()` in sync with real run flags
The current `swap()` recreates containers with only `--name` and `-p`. It does **not**
yet reproduce any bind mounts (e.g. the SQLite `demo.db`), config-file mounts, or
network/DNS-alias flags the real containers depend on. **Before relying on the script,
confirm the actual `podman run` flags** the current `backend-api` and `frontend-vue`
were created with and fold them into `swap()`, or a deploy will silently drop them.
To inspect what the running containers actually use:
```bash
podman --connection gcp-lab inspect backend-api | jq '.[0].Mounts, .[0].NetworkSettings'
podman --connection gcp-lab inspect frontend-vue | jq '.[0].Mounts, .[0].NetworkSettings'
```
## Caddy config / cert deploys
The Caddyfile and certs live on the host at `~/imperva-proxy/` and are bind-mounted
read-only into `caddy-proxy`. To change routing:
1. Edit `imperva-proxy/Caddyfile` in the repo, copy it to the host `~/imperva-proxy/`.
2. Validate before restarting so you're not debugging from a crash loop:
```bash
podman --connection gcp-lab exec caddy-proxy caddy validate --config /etc/caddy/Caddyfile
```
3. `./deploy.sh --restart-caddy`.
If the container is already crash-looping and `exec` won't attach, validate against the
mount with a throwaway container instead:
```bash
podman run --rm -v /home/matt/imperva-proxy/Caddyfile:/etc/caddy/Caddyfile:ro,Z \
docker.io/library/caddy:2-alpine caddy validate --config /etc/caddy/Caddyfile
```
## Verifying a deploy
- Origin health from the host (self-signed, so `-k`):
```bash
podman --connection gcp-lab exec caddy-proxy sh -c 'wget -qO- --no-check-certificate https://localhost/ | head'
```
(or `curl -vk https://localhost/` directly on the host)
- Real path end to end: load `https://flytoouter.space/` in a browser — should show
Imperva's trusted edge cert while the origin serves self-signed to Imperva only.