Pair Cloud Run with Firebase Hosting to generate and serve your
dynamic content or build REST APIs as microservices.

Using [Cloud Run](https://cloud.google.com/run/docs/), you can
deploy an application packaged in a container image. Then, using
Firebase Hosting, you can direct HTTPS requests to trigger your
containerized app.

- Cloud Run supports [several languages](https://cloud.google.com/run/docs/quickstarts/build-and-deploy#writing) (including Go, Node.js, Python, and Java), giving you the flexibility to use the programming language and framework of your choice.
- Cloud Run [automatically and horizontally scales](https://cloud.google.com/run/docs/about-concurrency) your container image to handle the received requests, then scales down when demand decreases.
- You only [pay](https://cloud.google.com/run/pricing) for the CPU, memory, and networking consumed during request handling.

**For example use cases and samples for Cloud Run integrated with
Firebase Hosting, visit our
[serverless overview](https://firebase.google.com/docs/hosting/serverless-overview.md.txt).**

<br />


This guide shows you how to:

1. [Write a simple Hello World application](https://firebase.google.com/docs/hosting/cloud-run.md.txt#write)
2. [Containerize an app and upload it to Artifact Registry](https://firebase.google.com/docs/hosting/cloud-run.md.txt#containerize)
3. [Deploy the container image to Cloud Run](https://firebase.google.com/docs/hosting/cloud-run.md.txt#deploy)
4. [Direct Hosting requests to your containerized app](https://firebase.google.com/docs/hosting/cloud-run.md.txt#direct_requests_to_container)

Note that to improve the performance of serving dynamic content, you can
optionally tune your [cache settings](https://firebase.google.com/docs/hosting/manage-cache.md.txt).

## Before you begin

Before using Cloud Run, you need to complete some initial tasks,
including setting up a Cloud Billing account, enabling the Cloud Run
API, and installing the `gcloud` command line tool.

### Set up billing for your project

Cloud Run offers [free usage quota](https://cloud.google.com/run/pricing),
but you still must have a
[Cloud Billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
associated with your Firebase project to use or try out Cloud Run.

> [!CAUTION]
> If your Firebase project is on the Spark pricing plan, and you associate your Firebase project with a Cloud Billing account, then your Firebase project is automatically upgraded to the Blaze pricing plan. Review the [Firebase pricing page](https://firebase.google.com/pricing.md.txt) for a comparison of the Spark and Blaze plans. Also, make sure to review Cloud Run [pricing](https://cloud.google.com/run/pricing) and its [quotas and limits](https://cloud.google.com/run/quotas).

> [!NOTE]
> **Note:** Every Firebase project is also a Google Cloud project.  
> Visit [Understand Firebase Projects](https://firebase.google.com/docs/projects/learn-more.md.txt#firebase-cloud-relationship) to learn more about the Firebase and Google Cloud project relationship.

### Enable the API and install the SDK

1. Enable the Cloud Run API in the Google APIs console:

   1. Open the
      [Cloud Run API page](https://console.cloud.google.com/apis/library/run.googleapis.com?project=_)
      in the Google APIs console.

   2. When prompted, select your Firebase project.

   3. Click **Enable** on the Cloud Run API page.

2. [Install and initialize](https://cloud.google.com/sdk/docs/) the
   Cloud SDK.

3. Check that the `gcloud` tool is configured for the correct project:

   ```
   gcloud config list
   ```

## **Step 1**: Write the sample application

Note that Cloud Run supports
[many other languages](https://cloud.google.com/run/docs/quickstarts/build-and-deploy#writing)
in addition to the languages shown in the following sample.

### Go

1. Create a new directory named `helloworld-go`, then change directory into
   it:

   ```
   mkdir helloworld-go
   ```

   ```
   cd helloworld-go
   ```
   1. Create a new file named `helloworld.go`, then add the following code:

   ```go
   package main

   import (
   	"fmt"
   	"log"
   	"net/http"
   	"os"
   )

   func handler(w http.ResponseWriter, r *http.Request) {
   	log.Print("helloworld: received a request")
   	target := os.Getenv("TARGET")
   	if target == "" {
   		target = "World"
   	}
   	fmt.Fprintf(w, "Hello %s!\n", target)
   }

   func main() {
   	log.Print("helloworld: starting server...")

   	http.HandleFunc("/", handler)

   	port := os.Getenv("PORT")
   	if port == "" {
   		port = "8080"
   	}

   	log.Printf("helloworld: listening on port %s", port)
   	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
   }
   ```

   This code creates a basic web server that listens on the port defined by
   the `PORT` environment variable.

Your app is finished and ready to be containerized and uploaded to
Artifact Registry.

### Node.js

1. Create a new directory named `helloworld-nodejs`, then change directory
   into it:

   ```
   mkdir helloworld-nodejs
   ```

   ```
   cd helloworld-nodejs
   ```
2. Create a `package.json` file with the following contents:

   ```javascript
   {
     "name": "knative-serving-helloworld",
     "version": "1.0.0",
     "description": "Simple hello world sample in Node",
     "main": "index.js",
     "scripts": {
       "start": "node index.js",
       "check": "node --check index.js"
     },
     "engines": {
       "node": ">=24"
     },
     "author": "",
     "license": "Apache-2.0",
     "dependencies": {
       "express": "^5.3.0"
     }
   }
   ```
3. Create a new file named `index.js`, then add the following code:

   ```javascript
   const express = require('express');
   const app = express();

   app.get('/', (req, res) => {
     console.log('Hello world received a request.');

     const target = process.env.TARGET || 'World';
     res.send(`Hello ${target}!\n`);
   });

   const port = process.env.PORT || 8080;
   app.listen(port, () => {
     console.log('Hello world listening on port', port);
   });
   ```

   This code creates a basic web server that listens on the port defined by
   the `PORT` environment variable.

Your app is finished and ready to be containerized and uploaded to
Artifact Registry.

### Python

1. Create a new directory named `helloworld-python`, then change directory
   into it:

   ```
   mkdir helloworld-python
   ```

   ```
   cd helloworld-python
   ```
2. Create a new file named `app.py`, then add the following code:

   ```python
   import os

   from flask import Flask

   app = Flask(__name__)


   @app.route("/")
   def hello_world():
       target = os.environ.get("TARGET", "World")
       return f"Hello {target}!\n"


   if __name__ == "__main__":
       app.run(host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))
   ```

   This code creates a basic web server that listens on the port defined by
   the `PORT` environment variable.

Your app is finished and ready to be containerized and uploaded to
Artifact Registry.

### Java

1. Install
   [Java SE 8 or later JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html)
   and [CURL](https://curl.haxx.se).

   Note that we only need to do this to create the new web project in the
   next step. The Dockerfile, which is described later, will load all
   dependencies into the container.
2. From the console, create a new empty web project using cURL then
   unzip commands:

   ```
   curl https://start.spring.io/starter.zip \
       -d dependencies=web \
       -d name=helloworld \
       -d artifactId=helloworld \
       -o helloworld.zip
   ```

   ```
   unzip helloworld.zip
   ```

   This creates a SpringBoot project.
3. Update the `SpringBootApplication` class in
   `src/main/java/com/example/helloworld/HelloworldApplication.java` by
   adding a `@RestController` to handle the `/` mapping and also add a
   `@Value` field to provide the `TARGET` environment variable:

   ```java
   package com.example.helloworld;

   import org.springframework.beans.factory.annotation.Value;
   import org.springframework.boot.SpringApplication;
   import org.springframework.boot.autoconfigure.SpringBootApplication;
   import org.springframework.web.bind.annotation.GetMapping;
   import org.springframework.web.bind.annotation.RestController;

   @SpringBootApplication
   public class HelloworldApplication {

     @Value("${TARGET:World}")
     String target;

     @RestController
     class HelloworldController {
       @GetMapping("/")
       String hello() {
         return "Hello " + target + "!";
       }
     }

     public static void main(String[] args) {
       SpringApplication.run(HelloworldApplication.class, args);
     }
   }
   ```

   This code creates a basic web server that listens on the port defined by
   the `PORT` environment variable.

Your app is finished and ready to be containerized and uploaded to
Artifact Registry.

## **Step 2** : Containerize an app and upload it to Artifact Registry

1. Containerize the sample app by creating a new file named `Dockerfile` in the
   same directory as the source files. Copy the following content into your
   file.

   ### Go


   ```go
   # Use the official Golang image to create a build artifact.
   # Use the supported Go 1.27 release track.
   FROM golang:1.27 AS builder

   ARG TARGETOS
   ARG TARGETARCH

   # Create and change to the app directory.
   WORKDIR /app

   # Copy local code to the container image.
   COPY . ./

   # Install dependencies and tidy up the go.mod and go.sum files.
   RUN go mod tidy

   # Build the binary.
   # -mod=readonly ensures immutable go.mod and go.sum in container builds.
   RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o server

   # Use the official Alpine image for a lean production container.
   # https://hub.docker.com/_/alpine
   # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
   FROM alpine:3.24
   RUN apk add --no-cache ca-certificates

   # Copy the binary to the production image from the builder stage.
   COPY --from=builder /app/server /server

   # Run the web service on container startup.
   CMD ["/server"]
   ```

   <br />

   ### Node.js


   ```javascript
   # Use the official lightweight Node.js 24 LTS image.
   # https://hub.docker.com/_/node
   FROM node:25-bookworm-slim

   # Create and change to the app directory.
   WORKDIR /usr/src/app

   # Copy application dependency manifests to the container image.
   # A wildcard is used to ensure both package.json AND package-lock.json are copied.
   # Copying this separately prevents re-running npm ci on every code change.
   COPY package*.json ./

   # Install production dependencies.
   RUN npm ci --omit=dev

   # Copy local code to the container image.
   COPY . ./

   # Run the web service on container startup.
   CMD [ "node", "index.js" ]
   ```

   <br />

   ### Python


   ```python
   # Use the official lightweight Python image.
   # https://hub.docker.com/_/python
   FROM python:3.15.0-slim-trixie

   # Allow statements and log messages to immediately appear in the Knative logs
   ENV PYTHONUNBUFFERED=1

   # Knative supplies PORT; default to 8080 for local runs.
   ENV PORT=8080
   WORKDIR /app

   # Install pinned production dependencies before copying application code.
   COPY requirements.txt ./
   RUN python -m pip install --no-cache-dir -r requirements.txt

   COPY app.py ./

   # Run the web service on container startup. Here we use the gunicorn
   # webserver, with one worker process and 8 threads.
   # For environments with multiple CPU cores, increase the number of workers
   # to be equal to the cores available.
   CMD ["sh", "-c", "exec gunicorn --bind \"0.0.0.0:${PORT:-8080}\" --workers 1 --threads 8 --timeout 0 app:app"]
   ```

   <br />

   ### Java


   ```java
   # Use the official Maven/Java 25 LTS image to build: https://hub.docker.com/_/maven
   FROM maven:3.9-eclipse-temurin-26 AS builder

   # Copy local code to the container image.
   WORKDIR /app
   COPY pom.xml .
   COPY src ./src

   # Build the executable jar.
   RUN mvn --batch-mode --no-transfer-progress package

   # Use the Eclipse Temurin Java 25 LTS runtime for the production stage.
   # https://hub.docker.com/_/eclipse-temurin
   # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
   FROM eclipse-temurin:25-jre-noble

   # Copy the jar to the production image from the builder stage.
   COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar

   # Run the web service on container startup.
   CMD ["java", "-jar", "/helloworld.jar"]
   ```

   <br />

2. Build your container image using Cloud Build by running the following
   command from the directory containing your Dockerfile:

   ```
   gcloud builds submit --tag gcr.io/PROJECT_ID/helloworld
   ```

   Upon success, you will see a SUCCESS message containing the image name  

   (`gcr.io/PROJECT_ID/helloworld`).

The container image is now stored in Artifact Registry and can be re-used if
desired.

Note that, instead of Cloud Build, you can use a locally installed version
of Docker to
[build your container locally](https://cloud.google.com/run/docs/building/containers#building_locally_and_pushing_using_docker).

## **Step 3** : Deploy the container image to Cloud Run

<br />

**Allowed Cloud Run regions**

<br />

For the best performance, colocate your Cloud Run service with
Hosting using the following regions:

- `us-west1`
- `us-central1`
- `us-east1`
- `europe-west1`
- `asia-east1`

Rewrites to Cloud Run from Hosting are supported in the following
regions:

- `asia-east1`
- `asia-east2`
- `asia-northeast1`
- `asia-northeast2`
- `asia-northeast3`
- `asia-south1`
- `asia-south2`
- `asia-southeast1`
- `asia-southeast2`
- `asia-southeast3`
- `australia-southeast1`
- `australia-southeast2`
- `europe-central2`
- `europe-north1`
- `europe-southwest1`
- `europe-west1`
- `europe-west12`
- `europe-west2`
- `europe-west3`
- `europe-west4`
- `europe-west6`
- `europe-west8`
- `europe-west9`
- `me-central1`
- `me-central2`
- `me-west1`
- `northamerica-northeast1`
- `northamerica-northeast2`
- `southamerica-east1`
- `southamerica-west1`
- `us-central1`
- `us-east1`
- `us-east4`
- `us-east5`
- `us-south1`
- `us-west1`
- `us-west2`
- `us-west3`
- `us-west4`
- `us-west1`
- `us-central1`
- `us-east1`
- `europe-west1`
- `asia-east1`

<br />

<br />

1. Deploy using the following command:

   <br />

   ```
   gcloud run deploy --image gcr.io/PROJECT_ID/helloworld
   ```

   <br />

2. When prompted:

   - Select a region (for example `us-central1`)
   - Confirm the service name (for example, `helloworld`)
   - Respond `Y` to **[allow unauthenticated invocations](https://cloud.google.com/run/docs/securing/authenticating#public)**

   > [!IMPORTANT]
   > **Important:** Remember the region and the service name so that you can use these values when defining the `region` and `serviceID`, respectively, in the [`firebase.json` rewrite rule](https://firebase.google.com/docs/hosting/cloud-run.md.txt#direct_requests_to_container) later.

3. Wait a few moments for the deploy to complete. On success, the command line
   displays the service URL. For example:
   `https://helloworld-RANDOM_HASH-us-central1.a.run.app`

4. Visit your deployed container by opening the service URL in a web browser.

> [!NOTE]
> **Note:** Consider applying a [maximum instance limit](https://cloud.google.com/run/docs/about-instance-autoscaling#max-instances) to prevent unexpected scaling of the Cloud Run service.

The next step walks you through how to access this containerized app ***from a
Firebase Hosting URL*** so that it can generate dynamic content for your
Firebase-hosted site.

## **Step 4:** Direct hosting requests to your containerized app

With
[rewrite rules](https://firebase.google.com/docs/hosting/full-config.md.txt#rewrites), you can direct requests
that match specific patterns to a single destination.

The following example shows how to direct all requests from the page
`/helloworld` on your Hosting site to trigger the startup and running of
your `helloworld` container instance.

1. Make sure that:

   - You have the
     [latest version of the Firebase CLI](https://firebase.google.com/docs/cli.md.txt#update-cli).

   - You have initialized Firebase Hosting.

   For detailed instructions about installing the CLI and initializing
   Hosting, see the
   [Get Started guide for Hosting](https://firebase.google.com/docs/hosting/quickstart.md.txt).
2. Open your [`firebase.json` file](https://firebase.google.com/docs/cli.md.txt#the_firebasejson_file).

3. Add the following `rewrite` configuration under the `hosting` section:

   ```
   "hosting": {
     // ...

     // Add the "rewrites" attribute within "hosting"
     "rewrites": [ {
       "source": "/helloworld",
       "run": {
         "serviceId": "helloworld",  // "service name" (from when you deployed the container image)
         "region": "us-central1",    // optional (if omitted, default is us-central1)
         "pinTag": true              // optional (see note below)
       }
     } ]
   }
   ```
4. Deploy your hosting configuration to your site by running the following
   command from the root of your project directory:

   ```
   firebase deploy --only hosting
   ```

<br />

**How `pinTag`
works within the `run` block**

<br />

> With this feature, you can ensure that the revision of your Cloud Run
> service for generating your site's dynamic content is kept in sync with your
> static Hosting resources and Hosting config. Also, this feature allows
> you to preview your rewrites to Cloud Run on Hosting preview
> channels.
>
> If you add `"pinTag": true` to a `run` block of the `hosting.rewrites`
> config, your static Hosting resources and configuration will be pinned to
> the most recent revision of the Cloud Run service, at the time of
> deploy. If you roll back a version of your site, the revision of the "pinned"
> Cloud Run service is also rolled back.
>
> This feature relies on
> [Cloud Run tags](https://cloud.google.com/run/docs/rollouts-rollbacks-traffic-migration#tags),
> which have a limit of 1000 tags per service and 2000 tags per region. This
> means that after hundreds of deploys, the oldest versions of a site may stop
> working.

<br />

<br />

Your container is now reachable via the following URLs:

- Your Firebase subdomains:  

  `PROJECT_ID.web.app/` and
  `PROJECT_ID.firebaseapp.com/`

- Any connected [custom domains](https://firebase.google.com/docs/hosting/custom-domain.md.txt):  

  `CUSTOM_DOMAIN/`

> [!NOTE]
> **Note:** Firebase Hosting is subject to a 60-second request timeout. If your app requires more than 60 seconds to run, you'll receive an HTTPS status code `504` (request timeout). To support dynamic content that requires longer compute time, consider using an [App Engine flexible environment](https://cloud.google.com/appengine/docs/flexible/).

Visit the Hosting configuration page for
[more details about rewrite rules](https://firebase.google.com/docs/hosting/full-config.md.txt#rewrites). You can
also learn about the
[priority order of responses](https://firebase.google.com/docs/hosting/full-config.md.txt#hosting_priority_order)
for various Hosting configurations.

## Test locally

During development, you can run and test your container image locally. For
detailed instructions, visit the
[Cloud Run documentation](https://cloud.google.com/run/docs/testing/local).

## Next steps

- [Set up caching](https://firebase.google.com/docs/hosting/manage-cache.md.txt) of your dynamic content on a
  global CDN.

- Interact with other Firebase services using the
  [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup.md.txt).

- Learn more about Cloud Run, including
  [detailed how-to guides](https://cloud.google.com/run/docs/how-to) for
  setting up, managing, and configuring containers.

- Review the [pricing](https://cloud.google.com/run/pricing) and the
  [quotas and limits](https://cloud.google.com/run/quotas) for
  Cloud Run.