Cloud Run accepts any image that follows its container runtime contract. The official Nub image supplies a supported linux/amd64 variant and runs as a non-root user.

Dockerfile

Dockerfile
FROM ghcr.io/nubjs/nub:0.6.0

COPY --chown=node:node package.json package-lock.json ./
RUN nub ci

COPY --chown=node:node . .
EXPOSE 8080
CMD ["nub", "run", "start"]

Change package-lock.json to the project's lockfile. The ingress container must listen on 0.0.0.0 at Cloud Run's PORT, which defaults to 8080:

server.ts
import { createServer } from "node:http";

const port = Number(process.env.PORT || 8080);
createServer((request, response) => {
  if (request.url !== "/alive") {
    response.writeHead(404);
    response.end();
    return;
  }

  response.writeHead(200, { "content-type": "application/json" });
  response.end(JSON.stringify({
    alive: true,
    runtime: "nub-on-node",
  }));
}).listen(port, "0.0.0.0");

Deploy from source

Authenticate the Google Cloud CLI, select a billing-enabled project, and enable the build and runtime services:

gcloud auth login
gcloud config set project PROJECT_ID
gcloud services enable \
  run.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com

The deployer needs Google's source-deployment roles. Source builds use the Compute Engine default service account unless the project selects another builder. A project administrator can grant the default account its required builder role:

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"

Deploy the directory containing the Dockerfile:

gcloud run deploy nub-app \
  --source . \
  --region us-west1 \
  --no-invoker-iam-check

Cloud Build builds the Dockerfile, stores the image in Artifact Registry, and creates a Cloud Run revision. The command prints the service URL when the revision is ready.

The public-access flag disables Cloud Run's invoker IAM check without adding an allUsers IAM binding, so it also works with domain-restricted sharing policies. Omit the flag for a private service.

Request the public service:

URL=$(gcloud run services describe nub-app \
  --region us-west1 \
  --format="value(status.url)")
curl --fail "$URL/alive"
# {"alive":true,"runtime":"nub-on-node"}

Authenticated callers need the Cloud Run Invoker role. Test a private service with an identity token:

URL=$(gcloud run services describe nub-app \
  --region us-west1 \
  --format="value(status.url)")
TOKEN=$(gcloud auth print-identity-token)
curl --fail -H "Authorization: Bearer $TOKEN" "$URL/alive"

Architecture

Cloud Run services require a linux/amd64 image. The official Nub tag is multi-architecture and includes that manifest, while a source deployment through Cloud Build produces the required architecture.

When building on an Apple Silicon computer instead, select the platform explicitly before pushing to Artifact Registry:

docker build --platform linux/amd64 -t IMAGE_URL .
docker push IMAGE_URL
gcloud run deploy nub-app --image IMAGE_URL --region us-west1

GitHub Actions

GitHub Actions can test with setup-nub, build the same Dockerfile, authenticate to Google through workload identity federation, and push the application image to Artifact Registry. No Cloud Run-specific Nub runtime or buildpack is required.