deploy-cloudrun
Skill0 starsChanged 21 days agoMITView on GitHub
Canonical Google Cloud Run deployment setup for the house web-app stack (React+Vite front-end behind nginx, FastAPI back-end, PowerShell deploy scripts, Artifact Registry). Use this whenever setting up deployment for a new project, writing or editing a deploy.ps1 / Dockerfile / nginx.conf for Cloud Run, adding a staging environment, fixing runtime-config (config.js) injection, or when a docker build fails on private dependencies — even if the user just says "deploy this" or "containerize the app".
- Reads credentials
- Installs packages
In this repository7 other files it gives its agents.
- gcp-bootstrapSkill
- gcp-migrate-dataSkill
- react-api-clientSkill
- react-app-scaffoldSkill
- react-auth-otpSkill
- react-seo-ssgSkill
- stack-auditSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 55
After a first all deploy, cross-wire the URLs: frontend VITE_API_URL → backend URL, backend CORS_ALLOWED_ORIGINS → frontend URL (the script …
- Very specific: line 102
Run the gcp-bootstrap skill first (APIs, worker SA, Firestore, bucket, registry).
- Very specific: line 106
Add vars/ and .config/ to .gitignore (they contain secrets).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Cloud Run Deployment (house stack)
Canonical deployment machinery extracted from brandician / perfect-resume / wine-your-sign.
The templates in assets/ are the single source of truth — copy them into the project and
adapt only the clearly-marked PROJECT CONFIG sections. If you improve a template here,
offer to propagate the change to the other projects using it.
Architecture
- One Docker image per service (back-end: python-slim + uvicorn; front-end: node build stage + nginx).
- Images pushed to Artifact Registry, deployed to Cloud Run (port 8080, non-root user).
- Runtime identity is always the project's
worker@<project>.iam.gserviceaccount.comservice account (created by thegcp-bootstrapskill). Never omit--service-account: Cloud Run would fall back to the default compute SA, which has no Firestore permission, and every request 403s. - Environment variables live in Cloud Run, seeded on first deploy from a gitignored
vars/<service>.yaml(created when$env:INITIAL_DEPLOY=1). Later deploys never touch env vars — change them in the Cloud Run console or with--env-vars-fileexplicitly. - Front-end config is injected at container start, not build time:
docker-entrypoint.shwrites/usr/share/nginx/html/config.js(window.ENV_CONFIG) from env vars, so the API URL can change without a rebuild. nginx must serveconfig.jsandindex.htmlwith no-cache headers (already in the template).
Templates (assets/)
| File | Copy to | Notes |
deploy.ps1 | project root | Multi-service deploy: local Docker or -CloudBuild, staging by default, -Production for prod names, -Account for a deployer SA, INITIAL_DEPLOY vars flow |
backend.dockerignore | back-end/.dockerignore AND back-end/.gcloudignore | Same content in both. Keeps .env*, .config/, vars/ out of the build (rule 8) |
frontend.dockerignore | front-end/.dockerignore AND front-end/.gcloudignore | Same content in both. Also excludes /back-end for a front-end at the repo root, plus agent/tool folders |
backend.Dockerfile | back-end/Dockerfile | python-slim, non-root httpd, vendored-wheels install; weasyprint system deps are marked optional |
frontend.Dockerfile | front-end/Dockerfile | node:22-alpine build (+ Chromium only if SSG prerendering), nginx:stable-alpine serve stage |
nginx.conf | front-end/nginx/nginx.conf | SPA routing with prerender support, no-cache for index.html AND /config.js, 1y asset caching, security headers, gzip, www→apex redirect |
docker-entrypoint.sh | front-end/docker-entrypoint.sh | Generates config.js from VITE_* env vars — extend the list per project |
vendor-shared-packages.ps1 | back-end/cmd_tools/ | Rebuilds the vendored wheels for the shared my-python packages |
Rules that prevent known failures
- Private pip dependencies are vendored as wheels, committed under
back-end/vendor/and referenced by./vendor/<name>.whllines in requirements.txt. Never usegit+https://pins to private repos:pip installruns inside the build container (even for localdocker build) where no git credentials exist. The Dockerfile mustCOPY ./vendor/ /app/vendor/beforepip install -r requirements.txt. - Staging first: without
-Production, services deploy asstaging-<name>. Keep prod and staging in the same project unless the project has outgrown it. - Line endings:
docker-entrypoint.shis authored on Windows — the frontend Dockerfile strips CRLF (dos2unix/sed) beforechmod +x. Don't remove that step. - Ports: everything listens on 8080 internally; Cloud Run maps 443 externally, so
nginx has
port_in_redirect off/absolute_redirect off. - After a first
alldeploy, cross-wire the URLs: frontendVITE_API_URL→ backend URL, backendCORS_ALLOWED_ORIGINS→ frontend URL (the script prints a reminder). - Frontend installs use
npm ci, nevernpm install --legacy-peer-deps: the legacy flag skips peer dependencies (e.g.@testing-library/dom, peer of@testing-library/reactv16), so the in-containertscgate fails on test files with TS2305 even though the identical build passes locally. Found in cat4-practice, 2026-07. - nginx no-cache headers must live in
location /, not onlylocation = /index.html:try_filesserves the SPA fallback as a file, bypassing the exact-match block, so/and deep links ship with noCache-Controland browsers heuristically cache a staleindex.htmlacross deploys. Repeat the securityadd_headers there too — anyadd_headerin a location suppresses the inherited server-level ones. Found in cat4-practice, 2026-07. - Secrets never enter a build context. Both Dockerfiles run
COPY . …, so every file the ignore files let through is uploaded and copied. Cloud Build uploads what.gcloudignoreallows (the tarball stays ings://<project>_cloudbuild/source/), anddocker buildcopies what.dockerignoreallows. Each service folder therefore needs BOTH files, with the same secret block:**/.env*,**/.config,/vars(the*.dockerignoretemplates are written to mean the same in both syntaxes; verified withgcloud meta list-files-for-uploadand aFROM scratchdocker build). ARUN rmafter the COPY does NOT help: the file stays in the COPY layer, readable by anyone who can pull the image. A front-end at the repo root must also exclude/back-end. Check withgcloud meta list-files-for-upload <dir>before the first deploy. Stronger still for a backend: an allow-list.dockerignore(*, then one!pathline per file or folder the image needs), as wine-your-sign.new'sbackend/uses. A key that already shipped in an image or upload must be rotated; excluding it only protects future builds. Found in train-to-negotiate, 2026-10: the root front-end's.gcloudignorelet.config/(deployer SA key, DNS key) through, andback-end/.dockerignorelet.env_pydanticthrough for local builds. - Never change the shared gcloud config. No
gcloud config set project|accountin scripts or by hand: every project on the machine shares it, so one project's deploy silently re-points the others. Pass--project(and--accountwhen a deployer SA is used) on every gcloud call;deploy.ps1does this through$Gc, and takes the project fromPROJECT CONFIG($DefaultProjectId,$DefaultAccount), never from the gcloud config. Docker's credential helper ignores--account, so the script setsCLOUDSDK_CORE_ACCOUNTonly arounddocker push. With an explicit account, Cloud Build is submitted--asyncand polled: streaming its logs needs project Viewer, which a deployer SA usually lacks, andgcloud builds submitthen exits 1 while the build keeps running (--suppress-logsdoes not avoid it). Found in train-to-negotiate, 2026-10. - No
$ErrorActionPreference = "Stop"in scripts that run gcloud/docker. Both write progress and warnings to stderr; Windows PowerShell 5.1 turns each stderr line into a terminatingNativeCommandErrorwhenever output is captured (coding agents, CI,tee), so the script dies on the first harmless warning. Use"Continue"and check$LASTEXITCODEafter every native command that matters. Same bug as gcp-bootstrap'ssetup-gcp.ps1(fixed 2026-07); hit again bydeploy.ps1in train-to-negotiate, 2026-10.
Setting up a new project
- Run the
gcp-bootstrapskill first (APIs, worker SA, Firestore, bucket, registry). - Copy the templates per the table above, including both ignore files per service; fill
each
PROJECT CONFIGblock (project id, service names, registry id, region, env-var seeds, domain in nginx.conf). - Add
vars/and.config/to .gitignore (they contain secrets); commit.example.yamlsiblings if useful. Rungcloud meta list-files-for-upload <service dir>and confirm no key or env file is listed. - First deploy:
$env:INITIAL_DEPLOY=1; .\deploy.ps1 all— edit the generated YAMLs when prompted. Subsequent deploys:.\deploy.ps1 all(add-CloudBuildwhen Docker Desktop isn't running).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

