Deploy to Cloud Run
Your site works locally. Now turn it into a public URL anyone can hit. Cloud Run takes your code, builds a container, runs it on demand, and scales to zero when nobody's looking — you only pay for actual traffic.
New to Docker and Cloud Run? · 90-second primer
Three pieces, one pipeline. None of them require Docker or any other tooling on your laptop — Google does the work in the cloud and hands you a URL.
- Docker is the standard way to package an app. You bundle your code plus everything it needs to run (Node 20, npm packages, system libraries) into one self-contained container image. The recipe for building that image is the
Dockerfilethe agent wrote in step 5. Once an image exists, it runs the same on your laptop, on a colleague's laptop, or on Google's servers — no "works on my machine." - Cloud Build is Google's container-building service. It reads your
Dockerfile, runs each step in the cloud, and produces an image. Then it pushes that image into Artifact Registry, which is essentially "GitHub for container images" — a per-project storage bucket for the images you build. - Cloud Run is the runner. When a request hits your public URL, Cloud Run pulls the latest image from Artifact Registry, starts a container, and routes the request. When traffic stops it shuts the container down. Next request restarts it in roughly one second. You only pay while a container is actively serving — idle is free. That's why a portfolio site that nobody hits at 3am costs you nothing.
The single command gcloud run deploy --source . orchestrates all three: it zips your project folder, hands it to Cloud Build (which uses your Dockerfile to build and push the image), then asks Cloud Run to roll out a new revision pointing at that image. One command, three services, ~3 minutes.
How this differs from "regular" hosting: Vercel/Netlify and similar are built around static-site builds. Cloud Run is built around any containerized app — the same flow ships a Node API, a Python ML service, a Rust binary, or our static site. The trade-off is one extra concept (the container) for an enormous range of what you can ship later.
Pre-flight check
Before you run the deploy, confirm the things you set up in step 1 are still in place. Two commands:
gcloud config get-value project
gcloud auth list
The first prints your project ID. The second shows the active Google account marked with *. If either is wrong, fix it before you deploy or you'll burn credits in the wrong place.
Review the Dockerfile
What's actually in a Dockerfile? · 30 sec
A Dockerfile is a plain-text recipe. Each line is one step in building the image: "start from this base image," "copy these files," "install these dependencies," "run this command when the container starts." Cloud Build reads it top to bottom, executes each step in a fresh sandbox, and produces a single image. The example below is the smallest one that works for our site — six steps, zero magic.
Base image (FROM) — what you're starting with. node:20-slim is a stripped-down Linux with Node 20 pre-installed. Working directory (WORKDIR) — where commands run inside the container. Copy (COPY) — moves files from your project into the image. Run (RUN) — executes a shell command at build time (e.g. npm ci). Expose (EXPOSE) — documents which port the container listens on. Cmd (CMD) — the command to execute at run time.
As part of scaffolding in step 5, the agent wrote a small Dockerfile at the project root. It looks roughly like this:
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]
In plain terms: start from a slim Node 20 image, install runtime dependencies, copy the project (your public/ folder + server.js), expose port 8080 (the Cloud Run default — the server reads process.env.PORT so it picks this up automatically), and run the server. Single-stage on purpose: the site is plain HTML/CSS, there's nothing to build.
Already comfortable with Node Dockerfiles? Skip ahead to the deploy section.
Confirm Cloud Build can deploy
Step 1C had you grant the Cloud Build service account the IAM role roles/run.builder. If you skipped that or you're not sure, run it now — it's idempotent (re-running is safe):
PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
--role="roles/run.builder"
Without this binding the first gcloud run deploy --source call fails with a permission error pointing at this exact role. Pre-staging it in setup is what makes the deploy block reliably finish in 20 minutes instead of 35.
Deploy the site
From the project root (the folder you scaffolded into in step 5), run:
gcloud run deploy --source .
--source . tells Cloud Run to package the current directory and ship it to Cloud Build, which builds the container, pushes it to Artifact Registry, and rolls it out as a Cloud Run service.
The first time you run it, gcloud asks four questions in order. Recommended answers for the workshop:
- Service name — press Enter to accept the default (the directory name) or type something like
career-site. The name becomes part of the public URL. - Region — Google's data centres are scattered worldwide; you pick one. Choose close to you so latency is low (
europe-west1for Belgium,us-central1for Iowa,asia-southeast1for Singapore). Cloud Run remembers this default for next time. - Create an Artifact Registry repository? — answer
Y. This creates the per-project storage bucket for your container images. One-time per project. - Allow unauthenticated invocations? — answer
yfor a public website. ("Unauthenticated" just means: anyone with the URL can hit it, no login required. Like a normal public webpage. The opposite — internal-only services — is for back-office tools where only your team should reach it.)
The first deploy takes 2–3 minutes — Cloud Build pulls base images, installs dependencies, and pushes the layers fresh. Subsequent deploys are much faster because the layers are cached.
Verify the public URL
When the deploy finishes, gcloud prints a Service URL on the last line of the output:
Service URL: https://career-site-xxxxxxxx-ew.a.run.app
The shape is always https://<service-name>-<hash>-<region-code>.a.run.app. Most terminals make it clickable — Cmd-click (macOS) or Ctrl-click (Windows/Linux) to open in your default browser. Or copy-paste it.
Open it and check:
- The page loads with your design, your projects, and your contact info from the Antigravity build.
- The FAQ section is visible.
- It works on mobile — pull it up on your phone.
Share the URL in the workshop chat. This is your live career site.
Closed the terminal? Scrolled past the output? Coming back tomorrow and don't remember it? Pick whichever's easiest:
- Ask gcloud directly — replace
career-siteand the region with yours:
Prints just the URL, nothing else.gcloud run services describe career-site --region europe-west1 --format='value(status.url)' - List all your services with their URLs:
gcloud run services list - Cloud Console — open the Cloud Run page, click your service, and the URL is at the top of the detail page with a copy button next to it.
Don't just paste the URL into a private DM. Drop it in the workshop channel so the rest of the room can see it. Then turn to the person next to you, say "this is mine — what do you think?", and click on theirs.
This is the deliverable. You shipped a real product on the same stack professional teams use. It's worth ~30 seconds of public acknowledgement before you move on.
One-line redeploy after changes
Once you've answered the prompts the first time, gcloud remembers your defaults. Iteration deploys are now a single command:
gcloud run deploy --source .
Press Enter through the prompts and Cloud Build does the rest.
Or: explicit one-shot command
Prefer to script it without prompts? This is the explicit form. Useful when you want to wire deploy into a CI job later:
gcloud run deploy career-site \
--source . \
--region europe-west1 \
--allow-unauthenticated \
--set-env-vars "GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project),LOCATION=europe-west1"
The env vars are pre-wired for the bonus chatbot — they cost nothing if you never use them.
You want to deploy the same site to a second region — say us-central1 — under a different service name career-site-us. Fill the blanks for the explicit form:
gcloud run deploy ____________ \
--source . \
--region ____________ \
--allow-unauthenticated
This is how the worked example sticks: you saw the full form once; you reproduce its shape with a different target. Kalyuga 2007 — d ≈ 0.5–1.0 for novices when a worked example is followed by a scaffolded reproduction.
Show the answer
gcloud run deploy career-site-us \
--source . \
--region us-central1 \
--allow-unauthenticated
Or: ask the agent to wrap it in a deploy.sh
Want a one-liner you can re-run without remembering flags? Ask the agent in step 5 (or now, if you go back) to write a deploy.sh wrapper at the project root that runs the explicit form above plus an APIs sanity-check. Then:
./deploy.sh
It's about 30 lines of shell — tiny enough to read top-to-bottom before you trust it.
Watch the logs (optional)
If something looks off after deploy, stream the recent logs from your service:
gcloud run services logs read career-site --region europe-west1 --limit 20
Optional: custom domain
Skip this if you don't already own a domain. Cloud Run can serve your site from a custom domain via domain mappings:
gcloud run domain-mappings create \
--service career-site \
--domain your-domain.com \
--region europe-west1
You'll need to add the DNS records Cloud Run shows you with your domain registrar. Propagation can take a few minutes to a few hours.
Troubleshooting
"Permission denied" on the Cloud Build service account during first deploy
Cause. On a fresh project, the Cloud Build service account doesn't yet have permission to create a Cloud Run service. This is the single most common first-deploy gotcha.
Fix. Grant the roles/run.builder role to the default compute service account, then retry the deploy:
PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
--role="roles/run.builder"
"API has not been used in project" / "API not enabled"
Cause. One of the required APIs is off, or the enable just propagated and your CLI cached the old state.
Fix. Re-run the enable from step 1 and try again:
gcloud services enable \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com \
aiplatform.googleapis.com
Cloud Build failed
Cause. Almost always a missing dependency in package.json or a Node version mismatch between local and Cloud Build.
Fix. Open the build log link in the failure message and read the actual error. Then either add the missing dep or pin Node:
// package.json
"engines": { "node": "20" }
Service deployed but returns 500
Cause. Usually missing env vars (GOOGLE_CLOUD_PROJECT, LOCATION) or the service hasn't been authenticated for Vertex AI.
Fix. Check Cloud Run logs, verify env vars in the Cloud Run revision settings, and confirm gcloud auth application-default login ran on your machine before deploy.
gcloud run services logs read SERVICE_NAME --region europe-west1 --limit 50
Container fails to start
Cause. Cloud Run injects a PORT env var (default 8080). If your server hardcodes a different port, the health check fails.
Fix. Listen on process.env.PORT:
app.listen(process.env.PORT || 3000);
Also check that your Dockerfile (if you have one) uses EXPOSE 8080 or omits EXPOSE entirely.
This deploy is intentionally public so you can share the URL. If you're curious about deploying private services with auth and IAM, the Deploy a Secure MCP Server on Cloud Run codelab walks through the same flow with --no-allow-unauthenticated and identity tokens.
gcloud run deploy --source .takes you from local code to a public HTTPS URL in one command.- Cloud Build packages the container; Cloud Run runs it and scales to zero when idle.
- One-time IAM grant for Cloud Build's service account is the most common first-deploy gotcha.
- Default region is remembered after the first deploy — re-deploys become a single Enter-through call.
- You can ask the agent to write a
./deploy.shwrapper if you'd rather not remember the flags.