# How to deploy a uv project to Azure Container Apps


[Azure Container Apps](https://learn.microsoft.com/en-us/azure/container-apps/overview) runs a container and scales it to zero when idle, the Azure counterpart to the request-driven container platforms in the [serverless comparison](https://pydevtools.com/handbook/explanation/about-running-python-on-serverless.md). Getting a [uv](https://pydevtools.com/handbook/reference/uv.md) project there is one command, `az containerapp up`, once two things are in place: a container that installs from the frozen [lockfile](https://pydevtools.com/handbook/explanation/what-is-a-lock-file.md), and a process that listens on the port the platform routes to.

This guide deploys a uv-managed web app (a FastAPI service on uvicorn) to Azure Container Apps.

## Write a uv Dockerfile for Container Apps

`az containerapp up --source .` can build Python source without a Dockerfile, but a Dockerfile gives control over the base image, a multi-stage build, and bytecode compilation. A builder stage installs dependencies with uv, and a slim runtime stage copies only the resulting virtual environment:

```dockerfile {filename="Dockerfile"}
# Build stage: resolve and install dependencies with uv
FROM python:3.13-slim AS builder

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

ENV UV_COMPILE_BYTECODE=1 \
    UV_LINK_MODE=copy

WORKDIR /app
RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --frozen --no-install-project --no-dev
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev --no-editable

# Runtime stage: copy only the virtual environment
FROM python:3.13-slim

RUN useradd --create-home appuser
WORKDIR /app
COPY --from=builder --chown=appuser:appuser /app/.venv /app/.venv

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

EXPOSE 8080
CMD ["uvicorn", "myapp.main:app", "--host", "0.0.0.0", "--port", "8080"]
```

`uv sync --frozen` installs the exact versions in `uv.lock` and fails if the lockfile is stale, so the deployed environment matches what you tested. `UV_COMPILE_BYTECODE=1` writes `.pyc` files at build time, which shortens startup when Container Apps boots a replica from zero. For the reasoning behind the two-step sync, see [How to use uv in a Dockerfile](https://pydevtools.com/handbook/how-to/how-to-use-uv-in-a-dockerfile.md).

> [!WARNING]
> The start command binds `--host 0.0.0.0`, not `127.0.0.1`. Container Apps forwards ingress traffic to the app over the container network, so a process bound to loopback is unreachable and the app never becomes healthy. The port (`8080`) must match the `--target-port` value passed at deploy time. The `EXPOSE 8080` line lets `az containerapp up` infer that port, so the two stay in sync.

## Deploy with a single `az containerapp up`

Install the Container Apps CLI extension and register the resource providers the deploy uses. Each runs once per subscription. `Microsoft.ContainerRegistry` is required because `up --source` builds the image in Azure Container Registry; a fresh subscription is not registered for it by default:

```bash
az extension add --name containerapp --upgrade
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights
az provider register --namespace Microsoft.ContainerRegistry
```

Run `az containerapp up` from the project root, where `pyproject.toml`, `uv.lock`, and the `Dockerfile` live. Because a Dockerfile is present, `up` builds from it instead of the buildpack:

```bash
az containerapp up \
  --name myapp \
  --resource-group myapp-rg \
  --source . \
  --ingress external \
  --target-port 8080
```

One command creates the resource group, a Container Apps environment named `myapp-env` with a Log Analytics workspace, an Azure Container Registry, then builds the image, pushes it, and deploys the app. The output prints the public URL. Ship later changes by rerunning with the same resource group and the environment it created:

```bash
az containerapp up \
  --name myapp \
  --source . \
  --resource-group myapp-rg \
  --environment myapp-env
```

## Keep a replica warm or let the app scale to zero

A new container app scales to zero by default: minimum replicas 0, maximum 10, with an HTTP scale rule that adds replicas as concurrent requests rise. No charges accrue while the app sits at zero. The cost is a cold start on the first request after idle, while Container Apps starts a replica and the app imports.

To keep one replica always running and avoid that cold start, set a floor:

```bash
az containerapp update \
  --name myapp \
  --resource-group myapp-rg \
  --min-replicas 1 \
  --max-replicas 10
```

> [!WARNING]
> An app with `--min-replicas 0` and no ingress has no scale trigger, so it scales to zero and cannot start again. The external ingress configured by `az containerapp up` supplies the HTTP scale rule that wakes the app, so keep ingress enabled or set `--min-replicas 1`.

## Pass secrets without baking them into the image

Keep API keys and connection strings out of the image and the lockfile. Store each value as a Container Apps secret, then reference it from an environment variable. A secret name is at most 20 characters and lowercase:

```bash
az containerapp secret set \
  --name myapp \
  --resource-group myapp-rg \
  --secrets api-key=s3cr3t-value

az containerapp update \
  --name myapp \
  --resource-group myapp-rg \
  --set-env-vars "API_KEY=secretref:api-key"
```

The `secretref:` prefix points the environment variable at the stored secret rather than an inline value. The app reads it with `os.environ["API_KEY"]`, and the secret stays out of image layers and the source upload.

## Learn more

- [Running Python on Serverless](https://pydevtools.com/handbook/explanation/about-running-python-on-serverless.md) compares Container Apps against Lambda, Cloud Run, Vercel, Modal, and the other scale-to-zero platforms.
- [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 build, cache mounts, and bytecode compilation in depth.
- [Deploy with the az containerapp up command](https://learn.microsoft.com/en-us/azure/container-apps/containerapp-up) documents every path `up` supports: existing image, local source, and GitHub repository.
- [Manage secrets in Azure Container Apps](https://learn.microsoft.com/en-us/azure/container-apps/manage-secrets) covers secret references, Key Vault integration, and volume mounts.
- [Scaling in Azure Container Apps](https://learn.microsoft.com/en-us/azure/container-apps/scale-app) documents replica limits, HTTP and custom scale rules, and the scale-to-zero behavior.
</content>
</invoke>
