# How to Deploy Air Applications with Docker and Kubernetes: Production Guide

> Deploy Air applications using Docker and Kubernetes. Build a multi-stage Docker image and apply production-ready Kubernetes manifests with custom probes and security contexts.

- Repository: [Feldroy/air](https://github.com/feldroy/air)
- Tags: production-guide
- Published: 2026-03-01

---

**Deploy Air applications by building a multi-stage Docker image using `examples/containerize/Dockerfile` and applying the production-ready Kubernetes manifest from [`examples/deployment/k8s/deployment.yaml`](https://github.com/feldroy/air/blob/main/examples/deployment/k8s/deployment.yaml) with customized health probes and security contexts.**

Air is a lightweight Python web framework built on **FastAPI**, **Pydantic**, and **HTMX** that runs on an **ASGI server** (typically *uvicorn*). The feldroy/air repository provides ready-to-use containerization and orchestration configurations that package Air apps as minimal containers and deploy them to Kubernetes clusters with proper health checks, security defaults, and multi-platform support.

## Containerizing Air Applications with Docker

The repository includes a production-hardened Dockerfile at `examples/containerize/Dockerfile` that implements a multi-stage build pattern. This approach separates dependency installation from the runtime environment to create minimal, secure images.

### Multi-Stage Build Configuration

The **build stage** uses the `ghcr.io/astral-sh/uv:python3.14-bookworm-slim` image to install Python dependencies into a virtual environment (`.venv`). The **runtime stage** then copies this environment and your application code into a stripped-down `python:3.14-slim-bookworm` image. This eliminates build tools from the final container, reducing attack surface and image size.

### Runtime Security and Health Checks

The runtime stage creates a non-root `appuser`, exposes port **8000**, and launches uvicorn with multiple workers. The container expects an HTTP endpoint at `/health` that returns a 2xx status code for orchestrator health verification. Air apps can expose this via a standard FastAPI route:

```python
import air

app = air.Air()

@app.get("/health")
def health() -> dict:
    return {"status": "ok"}

```

## Building Multi-Platform Images for Kubernetes

Because Kubernetes clusters often run mixed node architectures (AMD64 and ARM64), the Dockerfile supports **Docker Buildx** for creating cross-platform images. This ensures your Air application runs on any node pool without emulation overhead.

Enable BuildKit and create a multi-platform build:

```bash
export DOCKER_BUILDKIT=1

docker buildx create --use

docker buildx build \
    --platform linux/amd64,linux/arm64 \
    -t ghcr.io/your-user/air-app:1.0.0 \
    --push .

```

Push the resulting image to any container registry (Docker Hub, GitHub Container Registry, Quay, or private registries) so your Kubernetes cluster can pull it.

## Kubernetes Deployment Configuration

The manifest at [`examples/deployment/k8s/deployment.yaml`](https://github.com/feldroy/air/blob/main/examples/deployment/k8s/deployment.yaml) defines a complete, production-ready stack including **Deployment**, **Service**, **Ingress**, **ConfigMap**, and **Secret** objects. This allows a single `kubectl apply` command to deploy your entire Air application infrastructure.

### Core Manifest Components

- **Deployment**: Runs the Air container as a non-root pod with defined resource requests and limits
- **Service**: Exposes the application on port 80 within the cluster
- **Ingress**: Includes Traefik-specific annotations (replace with Nginx, Istio, or other ingress controller configurations as needed)
- **ConfigMap & Secret**: Inject configuration values and sensitive data without baking them into the image

### Health Probes and Security Context

The manifest configures **liveness** and **readiness** probes that query HTTP endpoints to determine pod health. While the sample uses `/`, you should customize these to point to your dedicated health endpoints:

```yaml
livenessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 5
  periodSeconds: 30

readinessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 5
  periodSeconds: 10

```

The security context runs containers as non-root with restricted permissions, following the principle of least privilege.

### Required Customization Points

Before applying the manifest, modify these specific fields in [`examples/deployment/k8s/deployment.yaml`](https://github.com/feldroy/air/blob/main/examples/deployment/k8s/deployment.yaml):

- **`metadata.name`**: Replace `air-app` with your service identifier
- **`spec.replicas`**: Set the desired number of pod instances
- **`containers.image`**: Update `hardwyrd/air-blogdemo:0.39.0` to your registry path and tag
- **Probe paths**: Change `/` to `/health` and `/ready` (or your custom endpoints)

## Production Deployment Workflow

Follow this sequence to deploy Air applications from code to cluster:

1. **Implement health endpoints** in your [`main.py`](https://github.com/feldroy/air/blob/main/main.py) (as shown in the Containerizing section).

2. **Build and push** the multi-platform image to your registry:
   ```bash
   docker buildx build \
       --platform linux/amd64,linux/arm64 \
       -t ghcr.io/your-user/your-air-app:latest \
       --push .
   ```

3. **Customize the manifest** by editing [`examples/deployment/k8s/deployment.yaml`](https://github.com/feldroy/air/blob/main/examples/deployment/k8s/deployment.yaml) with your application name, image reference, replica count, and probe paths.

4. **Deploy to the cluster**:
   ```bash
   kubectl apply -f examples/deployment/k8s/deployment.yaml -n your-namespace
   ```

5. **Verify the deployment**:
   ```bash
   kubectl get pods -n your-namespace
   kubectl describe pod <pod-name> -n your-namespace
   ```

6. **Test locally** (optional) before deploying:
   ```bash
   docker run --rm -p 8000:8000 ghcr.io/your-user/your-air-app:latest
   curl http://localhost:8000/health
   ```

## Summary

- The `examples/containerize/Dockerfile` provides a multi-stage build using `ghcr.io/astral-sh/uv` and `python:3.14-slim-bookworm` images to create minimal, secure runtime containers
- Air applications run as non-root users on port 8000 via uvicorn with multiple workers
- Docker Buildx enables multi-platform builds (`linux/amd64`, `linux/arm64`) for heterogeneous Kubernetes clusters
- The [`examples/deployment/k8s/deployment.yaml`](https://github.com/feldroy/air/blob/main/examples/deployment/k8s/deployment.yaml) manifest includes Deployments, Services, Ingress rules, and probe configurations ready for production customization
- Health endpoints at `/health` (or custom paths) enable proper Kubernetes liveness and readiness checks

## Frequently Asked Questions

### What base images does the Air Dockerfile use?

The build stage uses `ghcr.io/astral-sh/uv:python3.14-bookworm-slim` for dependency installation with the uv package manager, while the runtime stage uses `python:3.14-slim-bookworm` to provide a minimal Python environment. This combination ensures fast builds and small production images (approximately 100MB depending on dependencies).

### How do I configure health checks for Air in Kubernetes?

Add a FastAPI route to your [`main.py`](https://github.com/feldroy/air/blob/main/main.py) that returns HTTP 200, then reference this endpoint in the `livenessProbe` and `readinessProbe` sections of [`examples/deployment/k8s/deployment.yaml`](https://github.com/feldroy/air/blob/main/examples/deployment/k8s/deployment.yaml). The probes support custom paths, ports, initial delays, and check intervals to match your application's startup behavior.

### Can Air containers run as root users?

While technically possible, the provided Dockerfile explicitly creates a non-root `appuser` and the Kubernetes manifest runs containers with security contexts that discourage root execution. Running as root is unnecessary since uvicorn binds to port 8000 (unprivileged), and avoiding root reduces container escape risks.

### How do I expose my Air application to external traffic?

The sample manifest includes Ingress configuration with Traefik annotations. Replace these with annotations specific to your ingress controller (Nginx, Istio, AWS ALB, etc.) and update the host rules to match your domain. The Service object exposes port 80 internally, which the ingress controller routes to the pod's port 8000.