Flight-booking app scaffolded from the source running on the GCP host, plus the four-screen frontend and three OWASP attack endpoints (SQLi, reflected XSS, path traversal) built and locally verified against a live uvicorn/vite dev pair. Includes deploy.sh (local build -> ship over ssh -> swap on host) and docs/ carried over from the handoff.
4.0 KiB
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 rawsshbecause 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:
./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
- build —
podman buildthe backend and frontend images locally. - ship —
podman save <img> | ssh -i <key> matt@35.203.147.44 'podman load'for each image. - swap — over
--connection gcp-lab:rm -fthenrun -deach container with its name and port mapping. - 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:
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:
- Edit
imperva-proxy/Caddyfilein the repo, copy it to the host~/imperva-proxy/. - Validate before restarting so you're not debugging from a crash loop:
podman --connection gcp-lab exec caddy-proxy caddy validate --config /etc/caddy/Caddyfile ./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:
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):(orpodman --connection gcp-lab exec caddy-proxy sh -c 'wget -qO- --no-check-certificate https://localhost/ | head'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.