Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Multi-Container Pod

Write a multi-container pod spec in Kubernetes: learn the sync pattern, sidecar design, shared volumes, and inter-container communication with a hands-on exercise. Includes troubleshooting and edge cases.

Focus: write a multi-container pod spec

Sponsored

So you've mastered single-container Pods — they deploy, they run, they die. But real-world applications rarely live in isolation. A web server needs a log shipper tailing its files; a proxy needs to reload config from a sidecar that watches a ConfigMap; a batch job needs a helper container to stage data before the main process starts. Stitching these containers together with kubectl and Deployments separately is messy, fragile, and violates the atomic unit of scheduling Kubernetes gives you. The pain is real, and the solution is the multi-container Pod spec — one YAML, two (or more) containers, zero wasted coupling.

The problem this lesson solves

When you put two containers into one Pod, you're not just saving lines of YAML. You're solving the co-scheduling problem: two processes that must run on the same node, share the same network namespace, and access the same filesystem. Without this, you resort to: * Duplicate Deployments with podAntiAffinity hacks to force colocation — brittle and unpredictable. * Sidecars deployed as separate Pods that communicate over a Service, adding latency and operational debt. * emptyDir volumes shared via NFS or PVC mounts, which are overkill for transient data.

A multi-container Pod is the Kubernetes primitive that says: "these containers are born together, live together, and die together." Once you understand the spec, you unlock patterns — not workarounds.

Core concept / mental model

Think of a Pod as a shared envelope. Inside that envelope, every container: - Shares the Pod IP (no Service needed for local communication) - Shares the loopback interface (localhost resolves to the Pod IP) - Mounts the same volumes (defined at Pod level, not per-container) - Can communicate over inter-process communication (IPC) if shareProcessNamespace: true is set

Pro tip: Containers in the same Pod do not share port space — each container has its own network stack, but they both use the same IP. Port conflicts occur if two containers try to bind the same port inside the Pod. Use container-level containerPort fields to document intent, not enforce exclusivity.

The mental model to hold onto: a Pod is a single unit of scheduling, but a multi-tenant runtime.

The three common patterns

Pattern Description Use case
Sidecar A helper container that extends the functionality of the main container (e.g., log shipper, proxy, config reloader) nginx + fluentd tailing logs
Ambassador A container that acts as a proxy or adapter for the main container to an external service app + redis-client-sidecar
Adapter A container that transforms the output or format of the main container (e.g., metrics format converter) app + prometheus-adapter

How it works step by step

  1. Define shared volumes at Pod level — every block in spec.volumes is available to all containers.
  2. List each container inside spec.containers[] — each has its own image, command, ports, env, resources, and volumeMounts.
  3. Mount the same volume in different containers using unique mountPath values (can be same path, but beware of file conflicts).
  4. Optional: Enable process namespace sharing with shareProcessNamespace: true so containers can see each other's processes via /proc.
  5. Container startup order is not guaranteed — Kubernetes starts them all simultaneously. Use readinessProbes or wait-for scripts in an init container if sequencing is required.

Pro tip: For startup ordering, use an init container (not a regular container) to block until a condition is met, then let the main containers start.

Key YAML structure

apiVersion: v1
kind: Pod
metadata:
  name: multi-container-demo
spec:
  shareProcessNamespace: true
  volumes:
  - name: shared-data
    emptyDir: {}
  containers:
  - name: main-app
    image: nginx:latest
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
    ports:
    - containerPort: 80
  - name: log-sidecar
    image: busybox
    command: ["/bin/sh", "-c", "while true; do echo $(date) >> /logs/access.log; sleep 5; done"]
    volumeMounts:
    - name: shared-data
      mountPath: /logs

Hands-on walkthrough

Let's build a practical multi-container Pod: a web server that writes files to a shared volume, and a sidecar that monitors those files and logs changes.

Step 1: Create the Pod YAML

Save this as multi-pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: monitor-pod
spec:
  containers:
  - name: writer
    image: busybox
    command: ["/bin/sh", "-c"]
    args:
    - |
      while true; do
        echo "data at $(date)" >> /var/shared/output.txt
        sleep 3
      done
    volumeMounts:
    - name: workdir
      mountPath: /var/shared
  - name: reader
    image: busybox
    command: ["/bin/sh", "-c"]
    args:
    - |
      tail -f /var/shared/output.txt
    volumeMounts:
    - name: workdir
      mountPath: /var/shared
  volumes:
  - name: workdir
    emptyDir: {}

Step 2: Deploy and verify

kubectl apply -f multi-pod.yaml
kubectl get pods monitor-pod

Wait for Running status, then peek inside both containers:

# Check reader sidecar logs (it's tailing the file)
kubectl logs monitor-pod -c reader
# Sample output:
# data at Mon Mar 10 12:30:01 UTC 2025
# data at Mon Mar 10 12:30:04 UTC 2025

# Check writer container — same file, different mount path (no output, it just writes)
kubectl exec monitor-pod -c writer -- cat /var/shared/output.txt
# Should show same lines as reader log

Step 3: Inter-container communication via localhost

Add a third container that serves a web page from the shared volume, and another that curl's it.

apiVersion: v1
kind: Pod
metadata:
  name: web-pod
spec:
  containers:
  - name: web
    image: nginx:alpine
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  - name: curator
    image: curlimages/curl:7.88.1
    command: ["/bin/sh", "-c"]
    args:
    - |
      while true; do
        curl -s localhost:80/index.html
        sleep 10
      done
    volumeMounts:
    - name: html
      mountPath: /html
  volumes:
  - name: html
    emptyDir: {}
kubectl apply -f web-pod.yaml
kubectl logs web-pod -c curator
# Expected: HTML served by nginx (default welcome page)

Compare options / when to choose what

Approach Pros Cons Use when
Multi-container Pod Co-located, shared network/volumes, atomic lifecycle No horizontal scaling per container; restart policy shared Sidecar, ambassador, adapter patterns
Two separate Deployments + Service Independent scaling, separate lifecycle, different resource profiles Network round trip, Service discovery overhead, more YAML Microservices that need independent scaling
Init container + single main container Sequential startup, no process namespace pollution Only one workload type; no runtime cooperation Setup tasks (wait for DB, seed data)
Job + CronJob Batch execution, completions tracking Not long-lived; no persistent shared volumes One-shot sidecar tasks (rotate logs, backup)

When to choose a multi-container Pod: You need two processes that: - Must run on the same node (co-scheduling) - Share ephemeral data via emptyDir - Communicate via localhost for low latency - Have the same lifecycle (restart together)

Troubleshooting & edge cases

"CrashLoopBackOff" on one container, other runs fine

Check the logs of the failing container. The stable container will keep running, but the Pod will be in a "not ready" state if the failing container lacks a readinessProbe. Solution: add a readinessProbe to containers that are allowed to fail independently (rare).

Port conflict even though they're in the same Pod

Each container has its own network stack, but they share the Pod IP. Two containers cannot both listen on the same port within the Pod. For example, nginx using port 80 and another nginx sidecar also on port 80 will fail → Address already in use. Use different port numbers or use the second container to connect externally (not listen on the same port).

Volume file permissions / ownership

Busybox containers often run as root; nginx runs as nginx (UID 101). Write from busybox and read from nginx → permission denied. Solution: set securityContext.fsGroup at Pod level so that written files have group ownership readable by both containers.

spec:
  securityContext:
    fsGroup: 101  # match nginx's group

Container fails and Pod restarts — lose data?

Yes, emptyDir volumes are ephemeral — they are deleted when the Pod disappears. If the main container crashes and the Pod restarts (based on restartPolicy: Always), the volume is new. For persistent data between crashes, use hostPath (node-local) or PVC.

What you learned & what's next

You now know: - How to write a multi-container pod spec with YAML, including shared volumes and inter-container communication. - The mental model of a Pod as a shared envelope (network, volumes, lifecycle). - The three common patterns: sidecar, ambassador, adapter. - When to use a multi-container Pod vs separate Deployments. - How to troubleshoot port conflicts, volume permissions, and crash loops.

Next up: Dive into the init container pattern, where you can delay container startup until dependencies are met — perfect for waiting on databases or pre-staging data before the main app launches.

Practice recap

Mini-exercise: Write a multi-container Pod spec that runs a Python web app on port 5000 (image python:3.10-slim, serving a simple HTTP server) and a sidecar that tail -f the app's logs (mounted via shared emptyDir). Deploy it with kubectl apply, then exec into the sidecar to verify it sees the log output. Adjust the sidecar's command to filter only ERROR-level logs.

Common mistakes

  • Both containers try to bind the same port (e.g., port 80) — each container has its own network stack but they share the Pod IP; port conflict causes CrashLoopBackOff.
  • Forgetting that emptyDir is ephemeral — data is lost when the Pod restarts, not just when the container restarts.
  • Assuming containers start in the order they are defined — they start simultaneously; use init containers for sequencing.
  • Not setting securityContext.fsGroup when a non-root container reads files written by a root container results in Permission denied.

Variations

  1. Use hostPath volume instead of emptyDir for data that must survive Pod restarts, but be aware of node-binding.
  2. Enable shareProcessNamespace: true for containers that need to signal each other (e.g., via SIGTERM) or access /proc.
  3. Use a DaemonSet for sidecars that must run on every node, rather than bundling inside every Pod.

Real-world use cases

  • A web server (nginx) container paired with a log shipper (fluentd) sidecar that tails access logs from a shared emptyDir volume.
  • A main application container that needs a Redis cache sidecar on the same localhost for ultra-low-latency lookups, without network overhead.
  • A configuration reloader sidecar that watches a ConfigMap and signals the main nginx container via a shared process namespace to reload without restarting the Pod.

Key takeaways

  • A multi-container Pod is a co-scheduled set of containers that share network (localhost), volumes (Pod-level), and lifecycle.
  • Use spec.volumes to define shared storage once; each container mounts via volumeMounts at its own path.
  • Containers start simultaneously — use init containers or readiness probes for ordering dependencies.
  • The three canonical patterns are sidecar, ambassador, and adapter — each solves a different tight-coupling problem.
  • Port conflicts happen when two containers bind the same port inside the Pod — use different ports or talk via localhost on different ports.
  • Always set securityContext.fsGroup when sharing files between containers with different run-as users.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.