# How to deploy a uv project to Google Cloud Run


[Cloud Run](https://docs.cloud.google.com/run/docs) runs a container and scales it to zero when idle. Getting a [uv](https://pydevtools.com/handbook/reference/uv.md) project there comes down to one decision: let Google build the image from your source with its buildpack, or hand Cloud Run a Dockerfile you control. The same `gcloud run deploy --source .` command drives both. Two things decide whether the result runs: the [lockfile](https://pydevtools.com/handbook/explanation/what-is-a-lock-file.md) that the build installs from, and listening on the port Cloud Run assigns.

## Deploy from source with buildpacks

The fast path needs no Dockerfile. Commit `pyproject.toml` and `uv.lock`, leave `requirements.txt` out, and Cloud Run's Python buildpack installs with uv on its own. The buildpack activates uv whenever it finds a `uv.lock` next to a `pyproject.toml`, on every supported Python version ([Build a Python application](https://docs.cloud.google.com/docs/buildpacks/python)).

Add a web framework and an entrypoint the buildpack can start. A `Procfile` is the explicit choice, and its `web:` command must bind the port Cloud Run injects:

```bash
uv add fastapi "uvicorn[standard]"
```

```text {filename="Procfile"}
web: uvicorn main:app --host 0.0.0.0 --port ${PORT}
```

Lock the project, commit, and deploy. The first deploy in a project prompts to enable the Cloud Build and Cloud Run APIs:

```bash
uv lock
git add pyproject.toml uv.lock .python-version main.py Procfile
git commit -m "Add Cloud Run entrypoint"

gcloud run deploy myapp --source . --region us-central1 --allow-unauthenticated
```

Cloud Run uploads the source, the buildpack reads `uv.lock` and installs the exact locked versions with uv, and the service comes up at the URL the command prints.

The buildpack treats `requires-python` as a floor, not a pin. With `requires-python = ">=3.13"` it installs the latest available runtime, which is Python 3.14 today. To pin an exact version, commit a [`.python-version` file](https://pydevtools.com/handbook/explanation/what-is-a-python-version-file.md) with `uv python pin 3.13`; the buildpack reads it and builds on that version.

> [!WARNING]
> A `requirements.txt` in the project root takes precedence over `pyproject.toml`, and on Python 3.13 and earlier the buildpack reads it with pip, not uv. A stale `requirements.txt` left beside the lockfile silently bypasses uv. Delete it, or regenerate it from the lockfile with `uv export --frozen --no-dev --no-emit-project -o requirements.txt` so the two agree.

## Build a container with a Dockerfile

Reach for a Dockerfile when the buildpack's choices don't fit: a pinned base image, or a system library that a wheel links against. When a Dockerfile is present in the source root, `gcloud run deploy --source .` builds from it instead of the buildpack ([Deploy services from source code](https://docs.cloud.google.com/run/docs/deploying-source-code)).

Install uv, sync dependencies in two steps so the dependency layer caches separately from the source, and bind `0.0.0.0:$PORT` in the `CMD`:

```dockerfile {filename="Dockerfile"}
FROM python:3.13-slim

COPY --from=ghcr.io/astral-sh/uv:0.11.24 /uv /uvx /bin/

ENV UV_COMPILE_BYTECODE=1 \
    UV_LINK_MODE=copy

WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-install-project --no-dev
COPY . .
RUN uv sync --frozen --no-dev

ENV PATH="/app/.venv/bin:$PATH"

CMD exec uvicorn main:app --host 0.0.0.0 --port ${PORT:-8080}
```

The `--no-dev` flag drops [pytest](https://pydevtools.com/handbook/reference/pytest.md), [Ruff](https://pydevtools.com/handbook/reference/ruff.md), and other dev tools from the image, and `${PORT:-8080}` reads Cloud Run's port while still running locally.

> [!WARNING]
> The source build runs Cloud Build's classic Docker builder, which does not enable BuildKit. A Dockerfile that uses `RUN --mount=type=cache` or `--mount=type=bind` fails here with "the --mount option requires BuildKit." Use the plain `COPY` form above. To run a BuildKit Dockerfile (cache mounts, a multi-stage build that copies only `.venv`), build and push the image yourself, then deploy it with `gcloud run deploy myapp --image ...`. The multi-stage pattern and cache mounts are covered in [How to use uv in a Dockerfile](https://pydevtools.com/handbook/how-to/how-to-use-uv-in-a-dockerfile.md).

Deploy the same way:

```bash
gcloud run deploy myapp --source . --region us-central1 --allow-unauthenticated
```

## Choose between buildpacks and a Dockerfile

Default to the buildpack. It removes the Dockerfile from what you maintain, and Google patches the base image and rebuilds it without your involvement. Switch to a Dockerfile when the application needs something the buildpack can't express.

| Concern | Buildpack (`--source`, no Dockerfile) | Dockerfile |
| --- | --- | --- |
| Maintenance | Google owns the base image and patches it | You own the base image and updates |
| Base image | Fixed by the buildpack | Any image you choose |
| System libraries | Only what the buildpack provides | Install any `apt` package |
| Build steps | Dependency install only | Arbitrary `RUN` steps |
| Local parity | Build runs only in Cloud Build | Same image builds and runs anywhere |

## Make the container listen on Cloud Run's port

Cloud Run sets a `PORT` environment variable, defaults it to 8080, and routes requests to it. The app must listen on `0.0.0.0:$PORT` ([Container runtime contract](https://docs.cloud.google.com/run/docs/container-contract)). A server bound to `127.0.0.1` accepts only connections from inside the container, so Cloud Run's health check never reaches it and the deploy fails with a "container failed to start" error.

Both paths above read `$PORT` in the start command rather than hardcoding 8080, which is why each survives a port change. Web frameworks that default to localhost are the usual cause of a container that runs locally but fails on Cloud Run.

## Pass secrets with Secret Manager

Keep API keys and database passwords out of the image and the lockfile. Store each value in [Secret Manager](https://docs.cloud.google.com/secret-manager/docs/overview) and mount it at deploy time. Create the secret, then grant the service's runtime service account permission to read it:

```bash
echo -n "s3cr3t-value" | gcloud secrets create API_KEY --data-file=-

gcloud secrets add-iam-policy-binding API_KEY \
  --member="serviceAccount:$(gcloud projects describe "$(gcloud config get-value project)" \
    --format='value(projectNumber)')-compute@developer.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"
```

Mount the secret as an environment variable on deploy. `--set-secrets` maps `ENV_VAR=SECRET:VERSION`:

```bash
gcloud run deploy myapp --source . --region us-central1 \
  --set-secrets=API_KEY=API_KEY:latest
```

The application reads it like any other environment variable with `os.environ["API_KEY"]`. The value never enters the build, so it stays out of image layers and the source upload.

## Speed up cold starts in serverless containers

Cloud Run scales to zero, so a request after idle time pays a cold start: pull the image, start the container, import the app. Two uv settings shrink that cost, both already in the Dockerfile above.

`UV_COMPILE_BYTECODE=1` compiles `.pyc` files at build time instead of on first import. A scaled-from-zero container skips the bytecode-compile step, which matters most for apps with heavy import graphs. `uv sync --no-dev` keeps test and lint dependencies out of the runtime image, so there is less to pull and import.

For a buildpack deploy, the same image-size win comes from keeping `requirements.txt` absent so the build installs only the locked runtime dependencies from `uv.lock`, not a dev-inclusive export.

## Learn more

- [How to use uv in a Dockerfile](https://pydevtools.com/handbook/how-to/how-to-use-uv-in-a-dockerfile.md) covers the multi-stage pattern, `.dockerignore`, secret mounts, and base-image choices in depth.
- [How to deploy a uv project to AWS Fargate](https://pydevtools.com/handbook/how-to/how-to-deploy-a-uv-project-to-aws-fargate.md) is the long-lived-container sibling: ECR, CPU architecture, and registry build caching.
- [How to deploy a uv project to AWS Lambda](https://pydevtools.com/handbook/how-to/how-to-deploy-a-uv-project-to-aws-lambda.md) is the other serverless target, with handler images and cross-platform wheel builds.
- [Specify dependencies in Python](https://docs.cloud.google.com/run/docs/runtimes/python-dependencies) is Google's reference for how Cloud Run source deploys read `pyproject.toml`, `uv.lock`, and `requirements.txt`.
- [uv: A Complete Guide](https://pydevtools.com/handbook/explanation/uv-complete-guide.md) covers what uv does, how fast it is, and the core workflows.
