ServerlessBase Blog
  • Understanding Kubernetes Init Containers

    Init containers are special containers that run before your main application containers, ensuring prerequisites are met before deployment begins.

    Understanding Kubernetes Init Containers

    You've probably deployed a Kubernetes pod with multiple containers and wondered why one container seems to start before the others. That's not a bug—it's a feature called init containers. They're a powerful pattern for handling setup tasks that must complete before your application can safely start.

    Init containers are like the prep work before a dinner party. Your guests (main containers) can't arrive until the host (init containers) has set the table, poured the drinks, and lit the candles. If any of those tasks fail, the entire pod stays in a "pending" state until they succeed.

    What Are Init Containers?

    Init containers are regular containers that run to completion before any of your application containers start. They share the same pod network and filesystem as their sibling containers, but they're isolated in terms of lifecycle—they must finish successfully before the pod proceeds.

    Think of them as a mandatory checklist before your main application runs. Common use cases include:

    • Waiting for a database to be ready
    • Pulling secrets or configuration from a secure vault
    • Performing one-time setup tasks like migrations
    • Running health checks on dependencies

    Init Container Lifecycle

    The lifecycle of an init container is straightforward:

    1. The kubelet starts the init container
    2. The init container runs to completion
    3. If it exits successfully, the kubelet starts the main containers
    4. If it exits with a non-zero code, the kubelet restarts it

    This restart behavior continues until the init container succeeds. This ensures your application never starts until its prerequisites are truly ready.

    Init Container vs Regular Container

    The key difference lies in their lifecycle and purpose:

    FeatureInit ContainerRegular Container
    Execution orderRuns first, must completeRuns after init containers
    Restart policyAlways restarts until successFollows pod restart policy
    TerminationMust exit successfullyCan exit with any code
    PurposeSetup and prerequisitesApplication logic

    Init containers are essentially "setup containers" that don't contribute to your application's runtime—they're there to make sure everything is ready.

    Practical Example: Database Initialization

    Let's walk through a common scenario where init containers shine: ensuring a database is ready before your application connects.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web-app
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: web
      template:
        metadata:
          labels:
            app: web
        spec:
          initContainers:
          - name: wait-for-db
            image: busybox:1.28
            command:
            - sh
            - -c
            - |
              until nc -zz postgres-service 5432; do
                echo "Waiting for PostgreSQL..."
                sleep 2
              done
              echo "PostgreSQL is ready!"
          containers:
          - name: web
            image: nginx:latest
            ports:
            - containerPort: 80

    In this example, the init container uses busybox with nc (netcat) to check if the PostgreSQL service is reachable on port 5432. The pod won't start the nginx container until this check passes.

    Multiple Init Containers

    You can define multiple init containers in a single pod. They run sequentially—each one must complete successfully before the next begins. This allows for complex setup chains.

    initContainers:
    - name: init-db
      image: busybox:1.28
      command: ["sh", "-c", "nc -zz postgres 5432"]
    - name: init-cache
      image: busybox:1.28
      command: ["sh", "-c", "nc -zz redis 6379"]
    - name: init-config
      image: busybox:1.28
      command: ["sh", "-c", "curl -f http://config-server/api/config || exit 1"]

    Each init container builds on the work of the previous one, creating a robust setup pipeline.

    Init Container Resource Limits

    Just like regular containers, init containers can have resource requests and limits. This is important for ensuring your setup tasks don't starve your application containers of resources.

    initContainers:
    - name: wait-for-db
      image: busybox:1.28
      resources:
        requests:
          memory: "64Mi"
          cpu: "100m"
        limits:
          memory: "128Mi"
          cpu: "200m"

    However, keep in mind that init containers run to completion, so excessive resource usage during setup can delay pod startup.

    Init Container Environment Variables

    Init containers can use environment variables just like regular containers. This is useful for passing configuration from ConfigMaps or Secrets.

    initContainers:
    - name: setup
      image: alpine:3.18
      env:
      - name: DB_HOST
        valueFrom:
          configMapKeyRef:
            name: app-config
            key: db-host
      - name: DB_PASSWORD
        valueFrom:
          secretKeyRef:
            name: db-secrets
            key: password
      command: ["sh", "-c", "echo $DB_HOST $DB_PASSWORD"]

    Common Patterns

    1. Database Readiness Checks

    initContainers:
    - name: wait-for-postgres
      image: busybox:1.28
      command:
      - sh
      - -c
      - |
        until pg_isready -h $DB_HOST -p $DB_PORT; do
          echo "Waiting for PostgreSQL..."
          sleep 2
        done

    2. Migrations

    initContainers:
    - name: run-migrations
      image: myapp-migrations:latest
      env:
      - name: DATABASE_URL
        valueFrom:
          secretKeyRef:
            name: db-url
            key: value

    3. Waiting for External Services

    initContainers:
    - name: wait-for-api
      image: curlimages/curl:latest
      command:
      - sh
      - -c
      - |
        until curl -f http://api-service/health; do
          echo "Waiting for API service..."
          sleep 5
        done

    Troubleshooting Init Containers

    If your pod stays in "Pending" state, check the init container logs:

    kubectl describe pod <pod-name>

    Look for the "Init Containers" section to see exit codes and messages. Common issues include:

    • Wrong image name or tag
    • Network connectivity problems
    • Missing environment variables
    • Incorrect command syntax

    Init Container Best Practices

    1. Keep them simple: Init containers should do one thing well—setup.
    2. Use lightweight images: Busybox, alpine, or curlimages/curl are ideal.
    3. Add timeouts: Use timeout command to prevent hanging.
    4. Log progress: Print status messages so you can debug issues.
    5. Handle failures gracefully: Exit with non-zero code to trigger restarts.

    When to Use Init Containers

    Init containers are perfect for:

    • Database migrations and schema setup
    • Waiting for dependent services to be ready
    • Pulling secrets or configuration
    • Running one-time setup scripts
    • Performing health checks on prerequisites

    They're less ideal for:

    • Long-running background tasks (use sidecar containers instead)
    • Heavy computation that delays pod startup
    • Tasks that should run periodically (use CronJobs instead)

    Init Containers in Production

    In production environments, init containers become critical for reliability. They prevent your application from starting with incomplete configuration or connecting to unavailable services.

    Platforms like ServerlessBase handle init container execution automatically when you deploy applications, ensuring your prerequisites are met before traffic is routed to your services.

    Summary

    Init containers are a Kubernetes pattern that ensures your application starts only when it's truly ready. They run sequentially, must complete successfully, and provide a clean way to handle setup tasks.

    Key takeaways:

    • Init containers run before main containers and must complete successfully
    • They're ideal for database readiness checks, migrations, and dependency waiting
    • Multiple init containers run in sequence, each building on the previous
    • Keep them lightweight and focused on setup tasks
    • Use them to prevent your application from starting with incomplete prerequisites

    By using init containers, you create more robust deployments where your application can trust that its dependencies are available and properly configured before it begins processing requests.

    Leave comment