# Init Containers (Optional) (/docs/deploying-agent-factories-in-the-cloud/80-kubernetes-for-ai-services/16-init-containers-optional)

---
type: Document
title: "Init Containers (Optional)"
description: "Init containers run before application containers and must complete successfully before the app starts."
status: stable
order: 8016
chapter: 80
lesson: 16
duration_minutes: 35
generated:
  by: ag-archive/0.1.0
  at: 2026-08-26T00:00:00Z
ksor:
  audience: [public]
  approval:
    by: ag-archive/0.1.0
    at: 2026-08-26T00:00:00Z
trust_tier: unverified
build_id: sha256:c8912c3008ae345a7fdc33ace656531ad74f37c065177057c390c7d759f1e970
source_commit: dad4758bb59919c7b9130444778ade062a2dce3e
ksor_version: 0.0.47
---

Your AI agent from Chapter 79 is running on Kubernetes. But there's a timing problem: **the agent container starts before its dependencies are ready**.

Imagine deploying a sentiment analysis agent that needs a 500MB language model. If the container starts before the model is downloaded, the agent crashes immediately. You could add retry logic to the application, but that's fragile. Or you could use **init containers**—lightweight setup containers that run to completion before your main application container even starts.

Init containers solve the "dependencies ready" problem elegantly. They guarantee: download model weights, verify database connectivity, wait for configuration files—all before your production code runs.

---

## Why Init Containers?

### The Pod Startup Sequence

When you create a Pod, Kubernetes follows a strict sequence:

1. **Init containers run** (one at a time, in order)
2. **Wait for all init containers to succeed**
3. **Only then start app containers**

If any init container fails, Kubernetes doesn't start the app containers. The Pod restarts and tries again.

### When Init Containers Are Essential

**You should use init containers when:**
- Your app needs resources downloaded before starting (ML models, datasets, pre-compiled binaries)
- Your app requires other services to be ready (waiting for database, cache, or message queue)
- Your app needs shared configuration generated from secrets or ConfigMaps before startup
- Your app requires environment setup that can't be done in application code (special permissions, directory structures, file ownership)

**Common patterns in production:**
- Downloading and extracting model artifacts for ML services
- Checking database schema and running migrations before app starts
- Waiting for dependent services to be healthy
- Initializing shared volumes with static assets or configuration files
- Validating configuration files exist and are readable before main process starts

Compare this to your current approach: app container starts, tries to use a model that doesn't exist yet, crashes, Kubernetes restarts it, it crashes again. Repeated restart loops waste resources and delay startup.

### Example Problem: Agent Without Init Container

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: sentiment-agent
spec:
  containers:
  - name: agent
    image: sentiment-agent:1.0
    env:
    - name: MODEL_PATH
      value: /models/sentiment.bin
    volumeMounts:
    - name: models
      mountPath: /models
  volumes:
  - name: models
    emptyDir: {}
```

**Output when you deploy this:**
```
$ kubectl apply -f agent.yaml
pod/sentiment-agent created

$ kubectl logs sentiment-agent
Traceback (most recent call last):
  File "agent.py", line 12, in <module>
    model = load_model('/models/sentiment.bin')
FileNotFoundError: [Errno 2] No such file or directory: '/models/sentiment.bin'

$ kubectl describe pod sentiment-agent
...
Last State:     Terminated
  Reason:       Error
  Exit Code:    1
  Started:      2024-01-15T10:05:23Z
  Finished:     2024-01-15T10:05:24Z

Restart Count: 5
...
```

The Pod enters a crash loop. Your agent never starts successfully.

### Solution: Init Container Downloads the Model

With an init container, the model downloads before the agent even starts:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: sentiment-agent-ready
spec:
  initContainers:
  - name: download-model
    image: curlimages/curl
    command:
    - sh
    - -c
    - |
      echo "Downloading sentiment model..."
      curl -fsSL https://models.example.com/sentiment.bin \
        -o /models/sentiment.bin
      echo "Model ready: $(du -h /models/sentiment.bin)"
    volumeMounts:
    - name: models
      mountPath: /models
  containers:
  - name: agent
    image: sentiment-agent:1.0
    env:
    - name: MODEL_PATH
      value: /models/sentiment.bin
    volumeMounts:
    - name: models
      mountPath: /models
  volumes:
  - name: models
    emptyDir: {}
```

**Output when you deploy this:**
```
$ kubectl apply -f agent-with-init.yaml
pod/sentiment-agent-ready created

$ kubectl get pod sentiment-agent-ready
NAME                      READY   STATUS     RESTARTS   AGE
sentiment-agent-ready     0/1     Init:0/1   0          5s

$ sleep 10

$ kubectl get pod sentiment-agent-ready
NAME                      READY   STATUS     RESTARTS   AGE
sentiment-agent-ready     0/1     Init:0/1   0          15s

$ sleep 10

$ kubectl get pod sentiment-agent-ready
NAME                      READY   STATUS    RESTARTS   AGE
sentiment-agent-ready     1/1     Running   0          25s

$ kubectl logs sentiment-agent-ready
Model ready: 487M    /models/sentiment.bin
Agent starting on port 8000...
```

Notice the progression: `Init:0/1` (downloading) → `Running` (successful). The app container never starts until the model is ready. No crash loops. No retry logic in your application code.

---

## Creating Your First Init Container

Let's build a working init container step-by-step.

### Step 1: Understand the YAML Structure

Every init container is defined in the `initContainers` list within a Pod spec:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  initContainers:
  - name: init-1
    image: busybox
    command: ["sh", "-c", "echo 'First init'; sleep 2"]
  - name: init-2
    image: busybox
    command: ["sh", "-c", "echo 'Second init'"]
  containers:
  - name: app
    image: nginx
```

**Key points:**
- Init containers run in the order defined (init-1 completes before init-2 starts)
- The app container starts only after ALL init containers complete
- Init containers use the same image syntax as regular containers

### Step 2: Create a Real Example

Create a file `init-demo.yaml`:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: db-check-pod
spec:
  initContainers:
  - name: check-db
    image: alpine
    command:
    - sh
    - -c
    - |
      echo "Checking database connectivity..."
      # Simulate checking database
      if [ -f /tmp/db-ready ]; then
        echo "✓ Database is ready"
        exit 0
      else
        echo "✗ Database not ready, retrying..."
        # In real scenario: loop until connection succeeds
        exit 1
      fi
  containers:
  - name: app
    image: nginx:alpine
    ports:
    - containerPort: 80
```

### Step 3: Deploy and Observe

```bash
$ kubectl apply -f init-demo.yaml
pod/db-check-pod created

$ kubectl get pod db-check-pod
NAME           READY   STATUS     RESTARTS   AGE
db-check-pod   0/1     Init:0/1   0          3s
```

**Output shows:**
- `STATUS: Init:0/1` means "1 init container, 0 completed"
- Pod is waiting for the init container to finish

```bash
$ kubectl describe pod db-check-pod
...
Init Containers:
  check-db:
    Container ID:  docker://abc123...
    Image:         alpine
    Image ID:      docker-sha256:def456...
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      -c
      echo "Checking database connectivity..."...
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       Error
      Exit Code:    1
...
```

The init container exits with code 1 (failure). Kubernetes keeps retrying. Let's fix it:

```bash
# Create the condition the init container checks for
$ kubectl exec db-check-pod -- touch /tmp/db-ready 2>/dev/null || echo "Pod not running yet"
Pod not running yet

# Instead, delete and redeploy with the file pre-existing
$ kubectl delete pod db-check-pod
pod "db-check-pod" deleted
```

Create `init-demo-fixed.yaml` with a conditional that won't fail:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: db-check-pod-fixed
spec:
  initContainers:
  - name: check-db
    image: alpine
    command:
    - sh
    - -c
    - |
      echo "Checking database connectivity..."
      echo "✓ Database is ready"
      sleep 1
  containers:
  - name: app
    image: nginx:alpine
    ports:
    - containerPort: 80
```

```bash
$ kubectl apply -f init-demo-fixed.yaml
pod/db-check-pod-fixed created

$ kubectl get pod db-check-pod-fixed
NAME                 READY   STATUS    RESTARTS   AGE
db-check-pod-fixed   1/1     Running   0          5s

$ kubectl logs db-check-pod-fixed
Checking database connectivity...
✓ Database is ready
```

**Status progressed to `1/1 Running`** because the init container succeeded.

---

## Sharing Data Between Init and App Containers

Init containers are useful for setup, but they're even more powerful when they share data with app containers through volumes.

### Common Pattern: Download Then Use

Create `model-download-init.yaml`:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: model-loader
spec:
  initContainers:
  - name: download-model
    image: alpine
    command:
    - sh
    - -c
    - |
      echo "Downloading model..."
      # Create a dummy model file (simulate download)
      mkdir -p /models
      echo '{"version": "1.0", "weights": [...]}' > /models/config.json
      echo "Model downloaded to /models/config.json"
      ls -lh /models/
    volumeMounts:
    - name: model-storage
      mountPath: /models
  containers:
  - name: app
    image: alpine
    command:
    - sh
    - -c
    - |
      echo "App starting..."
      echo "Available models:"
      ls -lh /models/
      echo "Model content:"
      cat /models/config.json
      sleep 3600
    volumeMounts:
    - name: model-storage
      mountPath: /models
  volumes:
  - name: model-storage
    emptyDir: {}
```

**Key points:**
- Both `initContainers` and `containers` list `volumeMounts` for the same volume
- The init container writes to `/models`
- The app container reads from the same `/models`
- The volume (`emptyDir`) persists data across containers in the same Pod

### Deploy and Verify

```bash
$ kubectl apply -f model-download-init.yaml
pod/model-loader created

$ kubectl get pod model-loader
NAME           READY   STATUS    RESTARTS   AGE
model-loader   1/1     Running   0          8s

$ kubectl logs model-loader
App starting...
Available models:
total 4
-rw-r--r--    1 root     root           57 Jan 15 10:20 config.json
Model content:
{"version": "1.0", "weights": [...]}
```

The init container wrote the file; the app container successfully read it. **This is the pattern you'll use for:**
- Model download → model loading
- Database schema setup → app initialization
- Configuration validation → configuration usage

---

## Debugging Init Container Failures

What happens when an init container fails? Kubernetes provides tools to diagnose the problem.

### Scenario: Init Container Fails

Create `init-fail.yaml`:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: failing-init
spec:
  initContainers:
  - name: validate-config
    image: alpine
    command:
    - sh
    - -c
    - |
      echo "Validating configuration..."
      if grep -q "SECRET_KEY" /config/secrets.txt; then
        echo "Configuration valid"
        exit 0
      else
        echo "ERROR: SECRET_KEY not found in configuration"
        exit 1
      fi
    volumeMounts:
    - name: config
      mountPath: /config
  containers:
  - name: app
    image: alpine
    command: ["sleep", "3600"]
  volumes:
  - name: config
    emptyDir: {}
```

```bash
$ kubectl apply -f init-fail.yaml
pod/failing-init created

$ kubectl get pod failing-init
NAME            READY   STATUS    RESTARTS   AGE
failing-init    0/1     Init:0/1  2          10s

$ kubectl get pod failing-init
NAME            READY   STATUS    RESTARTS   AGE
failing-init    0/1     Init:0/1  3          15s
```

The Pod is stuck restarting because the init container keeps failing.

### Debugging Tool 1: kubectl logs with -c (container name)

```bash
$ kubectl logs failing-init -c validate-config
Validating configuration...
ERROR: SECRET_KEY not found in configuration

$ kubectl logs failing-init -c validate-config -p
# -p flag shows logs from previous init container run if it crashed
Validating configuration...
ERROR: SECRET_KEY not found in configuration
```

This tells you **why** the init container failed (missing SECRET_KEY).

### Debugging Tool 2: kubectl describe

```bash
$ kubectl describe pod failing-init
Name:             failing-init
Namespace:        default
Priority:         0
Node:             docker-desktop/192.168.65.4
Start Time:       Mon, 15 Jan 2024 10:25:00 +0000
Labels:           <none>
Annotations:      <none>
Status:           Pending
Init Containers:
  validate-config:
    Container ID:  docker://xyz789...
    Image:         alpine
    Image ID:      docker-sha256:abc123...
    Port:          <none>
    Host Port:     <none>
    Command:
      sh
      -c
      echo "Validating configuration..."...
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       Error
      Exit Code:    1
      Started:      2024-01-15T10:26:45Z
      Finished:     2024-01-15T10:26:46Z
    Ready:          False
    Restart Count:  3
    Environment:    <none>
    Mounts:
      /config from config (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-2q6nx (ro)
Events:
  Type     Reason     Age   From               Message
  ----     ------     ---   From               --------
  Normal   Scheduled  30s   default-scheduler  Successfully assigned default/failing-init to docker-desktop
  Normal   Created    28s   kubelet            Created container validate-config
  Normal   Started    28s   kubelet            Started container validate-config
  Normal   BackOff    15s   kubelet            Back-off restarting failed container
  Normal   Pulled     10s   kubelet            Container image "alpine:latest" pulled
  Normal   Created    9s    kubelet            Created container validate-config
  Normal   Started    9s    kubelet            Started container validate-config
  Normal   BackOff    3s    kubelet            Back-off restarting failed container
```

**Key diagnostic clues:**
- `State: Waiting` with `Reason: CrashLoopBackOff` = container keeps failing
- `Exit Code: 1` = exited with error
- `Restart Count: 3` = Kubernetes has retried 3 times

### The Fix

The init container is checking for a file that doesn't exist. Either:

1. **Create the file** (if this should work):
   ```bash
   # Can't exec into failing pod, so create a fixed version instead
   ```

2. **Fix the init container logic**:
   ```yaml
   initContainers:
   - name: validate-config
     image: alpine
     command:
     - sh
     - -c
     - |
       echo "Setting up configuration..."
       # Create the required file
       mkdir -p /config
       echo "SECRET_KEY=my-secret-12345" > /config/secrets.txt
       echo "Configuration created"
   ```

```bash
$ kubectl apply -f init-fixed.yaml
pod/fixed-init created

$ kubectl get pod fixed-init
NAME        READY   STATUS    RESTARTS   AGE
fixed-init  1/1     Running   0          5s

$ kubectl logs fixed-init -c validate-config
Setting up configuration...
Configuration created
```

Now the init container succeeds, and the Pod moves to `Running`.

---

## Advanced Init Container Patterns

### Pattern 1: Sequential Init Containers (Guaranteed Order)

Init containers always run sequentially. Use this when one setup step depends on another:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: sequential-setup
spec:
  initContainers:
  - name: 1-create-dirs
    image: alpine
    command: ["mkdir", "-p", "/app/logs", "/app/data"]
    volumeMounts:
    - name: app-storage
      mountPath: /app
  - name: 2-verify-config
    image: alpine
    command:
    - sh
    - -c
    - |
      echo "Checking for configuration..."
      test -f /config/app.conf
      echo "✓ Configuration verified"
    volumeMounts:
    - name: config
      mountPath: /config
  - name: 3-download-assets
    image: alpine
    command:
    - sh
    - -c
    - |
      echo "Downloading assets..."
      # Download step
      echo "✓ Assets ready"
    volumeMounts:
    - name: app-storage
      mountPath: /app
  containers:
  - name: app
    image: myapp:1.0
    volumeMounts:
    - name: app-storage
      mountPath: /app
    - name: config
      mountPath: /config
  volumes:
  - name: app-storage
    emptyDir: {}
  - name: config
    emptyDir: {}
```

The three init containers run in order: `1-create-dirs` completes, then `2-verify-config` runs, then `3-download-assets` runs. Only after all three succeed does the `app` container start.

### Pattern 2: Init Container with Retries

Some operations (like network requests) may be transient. Add retry logic to init containers:

```yaml
initContainers:
- name: download-with-retries
  image: alpine
  command:
  - sh
  - -c
  - |
    MAX_ATTEMPTS=5
    ATTEMPT=0
    while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
      ATTEMPT=$((ATTEMPT + 1))
      echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Downloading model..."

      if wget -q https://models.example.com/model.bin -O /models/model.bin; then
        echo "✓ Download successful"
        exit 0
      fi

      if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
        echo "  Retrying in 5 seconds..."
        sleep 5
      fi
    done

    echo "✗ Failed after $MAX_ATTEMPTS attempts"
    exit 1
  volumeMounts:
  - name: models
    mountPath: /models
```

This init container retries up to 5 times with a 5-second delay between attempts. Kubernetes will also restart the Pod after a delay if all attempts fail.

---

## Try With AI

You're deploying a custom computer vision model that requires:

1. Downloading a 2GB trained model from cloud storage
2. Extracting it from a tar.gz archive
3. Verifying the checksum before the app starts
4. Creating necessary directories with proper permissions

**Setup**: You have the model archive URL and expected SHA256 checksum available as environment variables.

**Your task**: Design an init container that handles all three steps, then configure the main app container to use the downloaded model.

**Specific prompts to try**:

1. "Design an init container YAML that downloads a 2GB model archive from `s3://models-bucket/vision-model.tar.gz`, extracts it to `/models`, and verifies the checksum is `abc123...`"

2. "Show me how to configure volume sharing so the app container (running a FastAPI service) can access the extracted model files at `/models/weights/`"

3. "What happens if the checksum verification fails? How should the init container handle this so Kubernetes knows to restart the Pod?"

4. "Write the complete Pod YAML combining the init container with a FastAPI app container that expects the model at `/models/weights/model.onnx`"

After you get responses, consider:
- What if the model download times out? How would you add retry logic to the init container command?
- How would you monitor how long the init phase takes before the app container starts?
- Could you use a ConfigMap to inject the model URL and checksum rather than hardcoding them?
