Deploy a Python App to Cloud Run
Containerize a Python Flask app and deploy it to Google Cloud Run.
Introduction
30 Second Summary
Every app starts on someone's laptop. The skill that separates a developer from a cloud engineer is knowing how to package that app and ship it to the internet so anyone can reach it.
In this project, you will build a Python web app, package it into a container with Docker, and deploy it to Cloud Run on Google Cloud. You'll then update the live service and see how Cloud Run manages versions and logging for you.
What You'll Build
A containerized Python Flask web app deployed to Cloud Run with a public HTTPS URL, managed entirely through the gcloud CLI.
By the end of this project, you'll have:
- A Python Flask web app packaged into a container image with Docker.
- An Artifact Registry repository storing your container image on Google Cloud.
- A live Cloud Run service with multiple revisions and a public URL you can share with anyone.
- A secret mission to lock down your service with IAM authentication and test with identity tokens.
Are there any prerequisites?
You'll need a Google Cloud account with billing enabled and Docker installed on your machine. You don't need Python installed locally since the Dockerfile handles the runtime. You'll install the gcloud CLI in Step 1.
Install and Configure the gcloud CLI
Every interaction with Google Cloud in this project happens through the gcloud CLI.
In this step, you'll install the CLI, log in to your Google account, and enable the APIs that Cloud Run, Cloud Build, and Artifact Registry need.
In this step, get ready to:
- Download and install the gcloud CLI.
- Log in to your Google account and select your GCP project.
- Enable the Cloud Run, Cloud Build, and Artifact Registry APIs.
Download and install the gcloud CLI
The gcloud CLI is a command-line tool that lets you create resources, deploy services, and manage your Google Cloud project from the terminal.
What is the gcloud CLI?
The gcloud CLI is the primary tool for interacting with Google Cloud Platform.
You'll use it throughout this project to build containers, deploy services, check revisions, and read logs.
Pro tip: macOS and Windows
The commands below are for Linux.
On macOS, the process is similar but uses a different archive file (e.g. google-cloud-cli-darwin-arm.tar.gz for Apple Silicon).
On Windows, download and run the GoogleCloudSDKInstaller.exe installer instead.
See the official install guide for platform-specific instructions.
- Open your terminal.
- Download and extract the gcloud CLI archive by running these commands:
curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz
tar -xf google-cloud-cli-linux-x86_64.tar.gz
What do these commands do?
- curl -O downloads the gcloud CLI archive from Google's servers.
- tar -xf extracts the archive into a folder called google-cloud-sdk.
- Run the install script. It will ask questions about your PATH and command completion:
./google-cloud-sdk/install.sh
- Answer Y when asked to add gcloud to your PATH.
- Answer Y when asked to enable command completion.
- Restart your terminal so the PATH changes take effect.
- Verify the installation by running:
gcloud --version
You should see output starting with Google Cloud SDK followed by a version number.
gcloud: command not found?
- Make sure you restarted your terminal after running the install script. The PATH update only applies to new terminal sessions.
- If you prefer not to restart, apply the changes manually by running source ~/.bashrc (or source ~/.zshrc if you use zsh).
- Confirm that you answered Y to add gcloud to your PATH during the install script. If you missed it, run ./google-cloud-sdk/install.sh again.
Help me troubleshoot gcloud: command not found after running install.sh
Initialize gcloud and authenticate
Now that gcloud is installed, you need to connect it to your Google account and GCP project.
- Start the initialization process by running:
gcloud init
What happens during gcloud init?
- A browser window opens asking you to sign in with your Google account.
- After signing in, the terminal prompts you to pick a GCP project. Select an existing project with billing enabled, or create a new one.
- If prompted to set a default Compute Engine region, you can skip it for now.
- Verify your setup by running:
gcloud config list
You should see your Google account email and project ID listed under [core].
Seeing the wrong project?
- You can switch projects at any time by running gcloud config set project YOUR_PROJECT_ID, replacing YOUR_PROJECT_ID with your actual project ID.
- Make sure your project has billing enabled. Some API calls will fail without it.
Help me fix my gcloud project configuration
Enable the required APIs
GCP APIs are disabled by default. You need to enable each service before your project can use it.
- Enable the Cloud Run, Cloud Build, and Artifact Registry APIs by running:
gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com
What does each API do?
- run.googleapis.com enables Cloud Run, where your containerized app will run.
- cloudbuild.googleapis.com enables Cloud Build, which builds your container image in the cloud.
- artifactregistry.googleapis.com enables Artifact Registry, which stores your container image.
This command may take a minute to complete. When it finishes with no errors, all three APIs are active.
Seeing a permission denied error?
- Make sure you are authenticated with an account that has the Owner or Editor role on the project.
- Confirm billing is enabled for your project. API enablement requires an active billing account.
Help me troubleshoot gcloud services enable permission errors
Your CLI is configured and your GCP project is ready. Next up, you'll write the Python app and its Dockerfile.
Write the Python App and Dockerfile
Your gcloud CLI is installed and connected to your GCP project. Now it's time to write the app you'll deploy to Cloud Run.
In this step, you'll create a Python web app with Flask and package it into a Docker container. Containers bundle your code with everything it needs to run, so it works the same on your laptop and in the cloud.
In this step, get ready to:
- Create a Python Flask app that responds to HTTP requests.
- Write a Dockerfile and requirements file to containerize the app.
- Test the container locally with Docker before deploying.
Create the project directory and app file
You need a dedicated folder for your project files. Cloud Run only has one hard requirement for your app: it must read the PORT environment variable that Cloud Run injects automatically, and listen on that port.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir cloud-run-app
cd cloud-run-app
- Confirm you're inside the new folder by running:
pwd
You should see a path ending in /cloud-run-app.
Seeing 'No such file or directory'?
Make sure you ran the mkdir command first. If you closed your terminal between the two commands, run cd ~/Desktop again before running mkdir cloud-run-app.
help me troubleshoot my mkdir command
- Open the cloud-run-app folder in your preferred text editor or IDE.
- Create a new file called app.py and add the following code:
import os
from flask import Flask
# Create the Flask app
app = Flask(__name__)
# Define a route for the root URL
@app.route("/")
def hello():
return "Hello from Cloud Run! This app is deployed on Google Cloud."
# Read the PORT environment variable that Cloud Run injects
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8080))
app.run(host="0.0.0.0", port=port)
What does this code do?
- Flask(__name__) creates a web application that can handle HTTP requests.
- The @app.route("/") decorator tells Flask to run the hello() function when someone visits the root URL.
- os.environ.get("PORT", 8080) reads the port number from an environment variable. Cloud Run sets this automatically when your app starts.
- app.run(host="0.0.0.0") makes the app listen on all network interfaces, which is required inside a container.
- Save app.py.
You won't see any output yet. You'll test this app after building the Docker container in the next substep.
Awesome, I've got everything!
Great. Double check you've saved app.py before moving on.
I'd like to double check the full code
import os
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello from Cloud Run! This app is deployed on Google Cloud."
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8080))
app.run(host="0.0.0.0", port=port)
Create the Dockerfile and requirements file
Your app needs two more files before it can be containerized. A requirements.txt tells pip which Python packages to install. A Dockerfile gives Docker the instructions for building your container image.
- Create a new file called requirements.txt in the cloud-run-app folder and add these dependencies:
flask==3.1.3
gunicorn==25.3.0
What is Gunicorn?
Flask comes with a built-in development server, but it is not designed for production traffic. Gunicorn is a production-grade WSGI server that handles multiple requests efficiently. Using Gunicorn in front of Flask is how real cloud engineers ship Python apps.
- Create another new file called Dockerfile (no file extension) in the cloud-run-app folder and add the following:
# Use a lightweight Python base image
FROM python:3.12-slim
# Set the working directory inside the container
WORKDIR /app
# Install dependencies first for better Docker layer caching
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application code
COPY . .
# Start the app with Gunicorn on port 8080
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "app:app"]
What does this Dockerfile do?
- python:3.12-slim is a lightweight base image. The slim variant strips out extras your app doesn't need, keeping the image small.
- Dependencies are copied and installed before the app code. This takes advantage of Docker's layer caching, so rebuilds are faster when only your code changes.
- --no-cache-dir prevents pip from storing download caches inside the image, keeping it even smaller.
- The CMD starts Gunicorn and tells it to serve your Flask app on port 8080.
- Save Dockerfile and requirements.txt.
- Confirm all three files exist by running this command in your terminal:
ls
You should see app.py, Dockerfile, and requirements.txt in the output.
Missing a file?
Make sure you saved each file inside the cloud-run-app folder, not in a parent or subfolder. Also check that Dockerfile has no file extension. It should be exactly Dockerfile, not Dockerfile.txt.
help me find my missing project files
Awesome, I've got everything!
Great. Make sure all three files are saved before moving on.
I'd like to double check the full code
flask==3.1.3
gunicorn==25.3.0
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "app:app"]
Test the container locally with Docker
Before deploying to the cloud, it's important to verify your container works on your own machine. This catches errors early and saves you from debugging in a remote environment.
- Build your container image by running this command:
docker build -t cloud-run-app .
This takes about a minute. When it finishes, you should see Successfully tagged cloud-run-app:latest near the end of the output.
What does this command do?
- docker build reads your Dockerfile and builds a container image from it.
- -t cloud-run-app tags the image with the name cloud-run-app so you can refer to it later.
- The . at the end tells Docker to look in the current directory for the Dockerfile and app files.
Seeing 'Cannot connect to the Docker daemon'?
Docker Desktop needs to be running before you can build images. Open Docker Desktop and wait for the engine to start (you'll see a green indicator in the bottom left). Then try the build command again.
If you see a different error, make sure your terminal is inside the cloud-run-app folder where your Dockerfile lives.
help me fix my Docker build error
- Start a container from your image by running this command:
docker run -p 8080:8080 -e PORT=8080 cloud-run-app
What does this command do?
- -p 8080:8080 maps port 8080 on your machine to port 8080 inside the container, so you can reach the app from your browser.
- -e PORT=8080 sets the PORT environment variable, simulating what Cloud Run does automatically in production.
- Open http://localhost:8080 in your browser.
You should see the message: Hello from Cloud Run! This app is deployed on Google Cloud.
- Go back to your terminal and press Ctrl+C to stop the container.
Browser showing 'This site can't be reached'?
- Check your terminal. If you see an error instead of Gunicorn startup logs, the container crashed. Read the error message for clues.
- Make sure nothing else is using port 8080. If another process is on that port, stop it first or change the port numbers in the docker run command.
- Verify you are visiting http://localhost:8080 (not https).
help me get my Docker container working locally
Your app runs locally and your container image is ready. Next up, you'll deploy it to Cloud Run and get a live public URL that anyone can visit.
Deploy to Cloud Run
Your app is containerized and tested locally. You confirmed it works on your machine.
In this step, you will deploy your container to Cloud Run with a single command. Cloud Run gives your app a public HTTPS URL, automatic scaling, and zero server management.
In this step, get ready to:
- Deploy your containerized app to Cloud Run with a single command.
- Visit your live service at its public URL.
- Explore your service and its first revision in the GCP Console.
Deploy from source with a single command
The gcloud run deploy --source . command is a shortcut that combines three steps into one. It builds your container image in the cloud, stores it, and deploys it to a live URL.
- In your terminal, confirm you are inside the cloud-run-app directory on your Desktop.
- Deploy your app to Cloud Run by running this command:
gcloud run deploy cloud-run-app --source . --region us-central1 --allow-unauthenticated
- When the command asks to create an Artifact Registry repository, enter y.
What does this command do?
- cloud-run-app is the name of your Cloud Run service.
- --source . tells Cloud Build to build a container image from the files in your current directory.
- --region us-central1 sets the deployment region.
- --allow-unauthenticated makes your service publicly accessible so anyone can visit the URL.
Behind the scenes, Cloud Build creates the container image, stores it in Artifact Registry, and deploys it to Cloud Run automatically.
- The deployment takes a few minutes. When it finishes, gcloud prints a Service URL that looks like https://cloud-run-app-xxxxx-uc.a.run.app.
- Copy this URL. You will open it in the next substep.
Seeing a permission or build error?
- If you see a PERMISSION_DENIED error, make sure you enabled the required APIs in Step 1 using the gcloud services enable command.
- If the build fails, confirm your Dockerfile, requirements.txt, and app.py are all saved inside the cloud-run-app directory.
- If you see a billing-related error, verify that billing is enabled for your GCP project in the Google Cloud Console.
Help me troubleshoot my Cloud Run deployment
Visit your live service
- Open the Service URL from the previous substep in your browser.
You should see the message: Hello from Cloud Run! This app is deployed on Google Cloud.
Your app is now live on the internet with a public HTTPS URL. Cloud Run handles scaling, TLS certificates, and load balancing for you automatically.
Explore your service in the GCP Console
The GCP Console gives you a visual overview of your Cloud Run service. It shows the revision history, traffic routing, and resource configuration.
- Open console.cloud.google.com in your browser.
- Click the navigation menu (the three horizontal lines in the top left corner).
- Scroll down and click Cloud Run.
- Click on your cloud-run-app service.
- Click the Revisions tab.
You should see one revision listed. The Traffic column shows 100%, meaning all incoming requests go to this single revision.
What is a revision?
Every time you deploy a new version of your app, Cloud Run creates a new revision. Each revision is an immutable snapshot of your service's code and configuration.
Old revisions stick around so you can roll back if something goes wrong. You can also split traffic between revisions for gradual rollouts.
Your service is live and you have explored it in the Console. Next up, you will update your app and see how Cloud Run handles new revisions automatically.
Update Your App and Manage Revisions
Your app is live on Cloud Run with a public HTTPS URL. In a real project, deploying once is just the beginning.
Every time you push a code change, Cloud Run creates a new revision of your service. In this step, you will update your app, see how Cloud Run manages revisions automatically, and read your service's logs.
In this step, get ready to:
- Modify your app's response message and redeploy to Cloud Run.
- List and inspect service revisions using the gcloud CLI.
- View live request logs in Cloud Logging.
Modify the app and redeploy
When you change your app's code and redeploy, Cloud Run does not replace your existing service. It creates a brand new revision and shifts all traffic to it.
- In app.py, find the hello() function.
- Find this line:
return "Hello from Cloud Run!"
- Replace the return string so the line reads:
return "Hello from Cloud Run! This app is deployed on Google Cloud."
Awesome, I've got everything!
Double check you have saved app.py before moving on.
I'd like to double check the full code
import os
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello from Cloud Run! This app is deployed on Google Cloud."
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8080))
app.run(host="0.0.0.0", port=port)
- Redeploy your updated app by running this command:
gcloud run deploy cloud-run-app --source . --region us-central1 --allow-unauthenticated
What does this command do?
This is the same deploy command you ran in the previous step. Behind the scenes, it:
- Sends your updated source code to Cloud Build, which builds a new container image.
- Pushes the new image to Artifact Registry.
- Creates a new Cloud Run revision and routes 100% of traffic to it automatically.
- Open the Service URL that gcloud prints at the end of the deployment output.
- You should see your updated message: Hello from Cloud Run! This app is deployed on Google Cloud..
Still seeing the old message?
- Make sure you saved app.py before running the deploy command. The command uploads the files on disk, not what is open in your editor.
- Try a hard refresh in your browser with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to bypass the cache.
- Check your terminal output for errors during the build step. If the build failed, the old revision stays active.
Help me troubleshoot my Cloud Run redeployment.
List and inspect revisions
Every deployment creates an immutable revision. Your service now has two revisions, and Cloud Run keeps the older one around in case you need to roll back.
- List all revisions of your service by running this command:
gcloud run revisions list --service cloud-run-app --region us-central1
What should I see?
You should see two revisions listed. The newer revision shows TRAFFIC: 100%, meaning all incoming requests go to your updated code.
The older revision still exists but receives no traffic. If something goes wrong with a new revision, you can instantly route traffic back to the previous one. This is how Cloud Run enables zero-downtime deployments.
View logs in Cloud Logging
In a real job, reading logs is how you diagnose problems with your services. Cloud Logging captures every request that hits your Cloud Run service automatically.
- View the last 20 log entries for your service by running this command:
gcloud run services logs read cloud-run-app --region us-central1 --limit 20
What do these logs tell me?
Each log entry shows details about a request that hit your service, including:
- The HTTP method and path (e.g. GET /).
- The status code (e.g. 200 for a successful response).
- The response time, which tells you how fast your service handled the request.
Cloud engineers spend a significant amount of time reading logs. Getting comfortable with this output now pays off in every future project.
Not seeing any logs?
- Logs can take a few seconds to appear. Wait 10-15 seconds, then run the command again.
- Make sure you have visited your Service URL at least once in the browser so there are requests to log.
- Check that your --region flag matches the region you deployed to (us-central1).
Help me read my Cloud Run logs.
Secret mission
Lock Down Your Service with IAM Authentication
Your service is currently public. Anyone with the URL can access it. Remove public access, lock it down with IAM authentication, and prove you can still reach your app with an authenticated request.
Before you go
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Cloud Run scales to zero when it is not receiving requests, so your service costs nothing while idle. Artifact Registry storage for a single small image is well within the free tier.
Resources you used:
- Cloud Run service (cloud-run-app) running in us-central1.
- Artifact Registry repository (cloud-run-source-deploy) storing your container image.
- Cloud Build minutes used to build your container image (120 free build-minutes per day).
- Local project directory (cloud-run-app) and Docker image on your machine.
Keep everything running
No action needed. Choose this if you are still actively building or want to experiment further with your Cloud Run service.
- Your Cloud Run service automatically scales to zero when it is not receiving traffic. You will not be charged while it is idle.
- Artifact Registry storage for a single small image is negligible and falls within the free tier.
- You can continue to redeploy and create new revisions at any time.
Pause - I'll come back to this later
Delete the Cloud Run service to free up your project's resource list, but keep your container image in Artifact Registry so you can redeploy quickly later.
- Delete the Cloud Run service:
gcloud run services delete cloud-run-app --region us-central1
- Your container image stays in Artifact Registry. When you are ready to redeploy, run the same gcloud run deploy command from your project directory.
- Keep your local cloud-run-app directory so you still have the source code.
Delete - I don't want to use this again
Remove all project resources and start fresh. This deletes your Cloud Run service, all its revisions, and the Artifact Registry repository.
- Delete the Cloud Run service and the Artifact Registry repository:
gcloud run services delete cloud-run-app --region us-central1
gcloud artifacts repositories delete cloud-run-source-deploy --location us-central1
- Remove the local Docker image:
docker rmi cloud-run-app
- If you created a new GCP project just for this project, you can delete the entire project instead:
gcloud projects delete PROJECT_ID
- Delete your local cloud-run-app directory if you no longer need the source code.
Mission accomplished
That's a Wrap
Nice work! You just went from an empty directory to a live, publicly accessible web app on Google Cloud. That is the core workflow of a cloud engineer.
Here is what you accomplished:
- Containerized a Python Flask app with Docker and deployed it to Cloud Run using a single gcloud run deploy --source . command.
- Updated a live service and watched Cloud Run create a new revision with zero-downtime traffic routing.
- Monitored your running service by reading logs with Cloud Logging through the gcloud CLI.
- Locked down your service with IAM authentication and tested access using identity tokens.
Ready to quiz yourself?