Deploying a site
部署站点
Two deploy targets — Cloud Run from source, or a standalone bundle behind nginx on a virtual machine — plus the audit gate, the least-privilege service account, the security headers, the secrets and the indexes each deploy depends on.
Deploy
A client site deploys to Cloud Run from source, or to a virtual machine as a standalone bundle behind nginx. This site runs on Cloud Run, and the script that deploys it is the one the starter template ships, so every deploy of the demo rehearses deploying a client.
Cloud Run
One script builds the container from the repository root and deploys it as a service in one region with a small instance cap. The script header lists the one-time project setup: enable the APIs, create the Firestore database and a media bucket, create the secrets, grant the runtime identity its roles, and deploy the Firestore indexes. The base URL is baked into the build for the sitemap and the auth cookie's salt, so a domain change is a redeploy.
Before every deploy
- the audit gate runs and refuses to ship with a high or critical advisory in a production dependency; exceptions are listed with a reason and an expiry date so they are revisited
- unit tests run without an emulator; integration tests run the full compose, save, edit and publish loop under the emulator suite
Least privilege
The service runs as a dedicated service account with three grants: Firestore user, object access on the one media bucket, and accessor on its own secrets. It is never the default compute account, which typically holds Editor on the whole project. Code running inside the container can read the secrets in its environment, which is exactly why the container's identity must be narrow.
Container and headers
The container runs as a non-root user. The framework's version header is off. Baseline security headers are set from the site configuration: no sniffing, a strict referrer policy, a permissions policy, same-origin framing and HSTS.
Secrets
The model key, the admin password and the embeddings key live in Secret Manager and reach the process as environment variables. An organisation-level model key also needs the workspace id as an environment variable. Nothing secret is in the repository, and the seed script reads the admin password from the environment.
Indexes
Every listing query has a composite index declared in a file in the repository, and the vector search has a vector index. They are deployed once per project. Without them the first archive page fails in production, so the deploy script's setup notes put this step before the first deploy.
The virtual machine target
The alternative script builds locally and installs a standalone bundle as a systemd service behind nginx, with sample unit, nginx and environment files. Media then lives on a persistent disk outside the release tree through the local blob adapter. This is the target for clients who already run a machine.
Why the hardening is not optional
The site this mechanism grew out of was compromised through a remote code execution flaw in an unpatched framework version that the dependency audit had been flagging for months. Managed hosting patches the host, not the runtime inside the container. The audit gate turns red before a deploy rather than after.