Alright, let’s get this straight: a Pod is the smallest deployable unit in Kubernetes, but it’s not always a single container. Think of a Pod not as a container, but as a logical host for one or more containers. It’s a shared execution environment where these containers cohabitate, sharing things like network namespace and storage volumes. They’re roommates, not neighbors in separate apartments.

The single-container Pod is the easy one. It’s the default, the “hello world” of Kubernetes. You ask for an nginx Pod, and Kubernetes gives you a Pod with a single nginx container inside it. It’s simple, it’s clean, and for 90% of your applications, it’s all you’ll ever need. The Pod is essentially just a wrapper around your container, giving it the superpowers of being a Kubernetes object—things like health checks, lifecycle management, and a stable IP address.

But then, there’s the multi-container Pod. This is where things get interesting, and where a lot of the “why” of Kubernetes design becomes clear. We don’t just shove random containers together because we can. We do it when those containers have a tightly coupled symbiotic relationship. They are separate processes that must be deployed, scaled, and retired together as a single unit.

When to Use a Multi-Container Pod

You use this pattern when two containers need to share more than just a polite handshake over a network call. The classic example is a web server and a logging agent. The web server writes logs to a shared volume, and the logging agent tails those logs and ships them off to some central system like Elasticsearch. They share a disk. They need to be on the same node to access that disk. They are a unit.

Another prime example is an adapter or ambassador pattern. Imagine your main application container is a simple service that only knows how to speak HTTP. But it needs to talk to a database that requires a special, proprietary proxy. Instead of bloating your application container with that proxy logic, you run the proxy in a second container, right beside the app. They share the network namespace, so your app can just talk to localhost and the proxy container will pick it up. It’s a beautiful separation of concerns.

Here’s a concrete YAML example. Notice the volumeMounts that create the shared space between the two containers.

apiVersion: v1
kind: Pod
metadata:
  name: web-server-and-logger
spec:
  volumes:
    - name: shared-logs
      emptyDir: {} # A simple temporary directory shared between containers
  containers:
    - name: web-server
      image: nginx:alpine
      volumeMounts:
        - name: shared-logs
          mountPath: /var/log/nginx
    - name: log-shipper
      image: busybox
      args: [/bin/sh, -c, 'tail -f /var/log/nginx/access.log'] # Just an example
      volumeMounts:
        - name: shared-logs
          mountPath: /var/log/nginx

The Shared-Namespace Reality

This is the core of the multi-container Pod magic. Containers within a Pod share two key Linux namespaces:

  • Network Namespace: They all share the same IP address and port space. This is why you can have your app talk to localhost:9200 to reach the Elasticsearch sidecar in the same Pod. It also means you cannot have two containers trying to bind to the same port—they’d fight over it and one would crash. Classic roommate drama.
  • UTS Namespace: They share the same hostname. It’s set to the Pod’s name.

They do not, however, share the PID namespace by default. Your containers can’t see each other’s processes unless you explicitly enable this via shareProcessNamespace: true in the Pod spec. This is a security feature, preventing a compromised sidecar from snooping on your main application’s processes.

The Init Container: The Special Cousin

Before we talk about the main containers, we have to talk about their fussy older sibling: the Init Container. These guys run to completion, in a strict sequence, before your main app containers are ever started.

Why? Because sometimes your main app has prerequisites. It needs a database schema loaded, a secret downloaded from a secure vault, or some necessary binary pulled onto a shared volume. Init containers are the perfect tool for this. Their failure will prevent the Pod from starting, which is exactly what you want. You don’t want your app container frantically retrying its database connection because the schema was never loaded.

apiVersion: v1
kind: Pod
metadata:
  name: app-with-init
spec:
  containers:
    - name: main-app
      image: my-app:latest
      volumeMounts:
        - name: config-data
          mountPath: /app/config
  initContainers:
    - name: download-config
      image: curlimages/curl:latest
      command: ['sh', '-c', 'curl -o /app-config/config.yaml https://config-server/config']
      volumeMounts:
        - name: config-data
          mountPath: /app-config
  volumes:
    - name: config-data
      emptyDir: {}

The Pitfalls and Sharp Edges

This pattern is powerful, but it’s not free. The biggest gotcha is that you can’t update or scale the containers individually. You scale the entire Pod. If you need to change a single flag on your log-shipper sidecar, you have to redeploy the entire Pod, taking your web server down with it. This is by design, but it’s a design that demands respect. Don’t use a multi-container Pod for things that should be separate microservices communicating over a network. Use it for truly inseparable pairs.

Also, resource limits are a bit trickier. The Pod’s overall resource requests and limits are the sum of all its containers. Kubernetes uses this to schedule the Pod, but it also uses the individual container requests for quality-of-service (QoS) classification. If you have a main container with a large request and a sidecar with a small one, the scheduler finds a node with capacity for the sum, but the main container is treated as the important one. It’s a subtle but important distinction.

So, the rule of thumb: if your containers can live on different nodes and communicate via a service IP, they should be separate Pods. If they need to share a disk or a local network connection to function, then and only then should they be welded together in a multi-container Pod. It’s a sharp tool. Use it precisely.