| Tested on | Rocky Linux 10.2 (Red Quartz) workstation |
|---|---|
| Package | kubectl 1.36.3 |
| Applies to | Any host with kubectl configured; Kubernetes v1.33 or later |
| Cert prep | CKAD |
| Lab environment | Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm |
| Privilege | Normal user (no sudo required on the workstation) |
| Scope | Multi-container Pods, localhost and shared-volume communication, native sidecar lab, adapter and ambassador patterns, startup behaviour, resource overview, and common problems. Does not cover init-container sequencing depth, Jobs, service mesh, Ingress, PV provisioning, or resource limits depth. |
| Related guides | Kubernetes health probes |
A sidecar container runs beside your main application in the same Pod, sharing its network namespace and optional volumes. This walkthrough builds one native sidecar Pod in the sidecar-lab namespace: the content-writer sidecar keeps index.html fresh on a shared emptyDir volume while nginx serves it over HTTP. Native sidecars are stable from Kubernetes v1.33. They were available as a beta feature and enabled by default in Kubernetes v1.29 through v1.32.
What Is a Multi-Container Pod?
A Pod can run more than one tightly coupled container.
- Containers belong to the same Pod, run on the same node, and are created or removed as one application unit
- They can share files by mounting the same volume
- Each container still has its own image, filesystem, and process environment
- The one-container-per-Pod model remains appropriate when components do not need close coordination
For Pod YAML, phases, and lifecycle detail, see Kubernetes Pods and Pod Lifecycle.
How Containers Communicate Inside a Pod
Shared network
All containers in a Pod use the same Pod IP address and port namespace.
- Processes reach each other on
localhost - Each listener must use a different port inside the Pod
- No separate Service is required for container-to-container traffic within the Pod
If nginx listens on port 80 in the web container, another container can fetch it with http://localhost:80.
Shared volumes
Container filesystems are separate. To exchange files, both containers mount the same named volume at paths that suit each image:
- Sidecar writes to
/shared/index.html - Application reads from
/usr/share/nginx/html/index.html - The volume name (
shared) ties the mounts together
spec:
initContainers:
- name: content-writer
volumeMounts:
- name: shared
mountPath: /shared
containers:
- name: web
volumeMounts:
- name: shared
mountPath: /usr/share/nginx/html
volumes:
- name: shared
emptyDir: {}For volume types and persistence beyond scratch space, see Kubernetes volumes.
Kubernetes Sidecar Pattern
A sidecar extends or supports the main application without replacing its primary function.
| Use case | Example helper |
|---|---|
| Log processing | Fluent Bit or Filebeat beside the app container |
| Configuration sync | Container that refreshes config files on a shared volume |
| Local proxying | Ambassador that forwards localhost traffic to a remote API |
| Metrics collection | Prometheus exporter scraping localhost metrics |
| File generation | content-writer in this lab, updating index.html on a shared volume |
Create a native sidecar using a shared volume
Kubernetes v1.33 made native sidecars stable. The primary lab uses a native sidecar declared under spec.initContainers with container-level restartPolicy: Always. Save this manifest as sidecar-web.yaml:
apiVersion: v1
kind: Pod
metadata:
name: web
namespace: sidecar-lab
spec:
initContainers:
- name: content-writer
image: busybox:1.36
restartPolicy: Always
command:
- sh
- -c
args:
- |
while true; do
message="Sidecar updated at $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "$message" | tee /shared/index.html
sleep 30
done
startupProbe:
exec:
command:
- sh
- -c
- test -s /shared/index.html
periodSeconds: 1
failureThreshold: 30
volumeMounts:
- name: shared
mountPath: /shared
containers:
- name: web
image: nginx:1.27-alpine
volumeMounts:
- name: shared
mountPath: /usr/share/nginx/html
volumes:
- name: shared
emptyDir: {}The sidecar writes index.html to /shared; nginx serves the same file from /usr/share/nginx/html because both mount the shared volume. The startupProbe ensures the sidecar has created index.html before Kubernetes starts the web container. Native sidecars start in init-container order, and Kubernetes proceeds after the sidecar has started or its startup probe succeeds.
Create the namespace:
kubectl create namespace sidecar-labSample output:
namespace/sidecar-lab createdApply the Pod:
kubectl apply -f sidecar-web.yamlSample output:
pod/web createdWait until both containers are Ready before you inspect the Pod:
kubectl wait --for=condition=Ready pod/web -n sidecar-lab --timeout=60sSample output:
pod/web condition metConfirm both containers are running:
kubectl get pod web -n sidecar-labSample output:
NAME READY STATUS RESTARTS AGE
web 2/2 Running 0 3sThe 2/2 count includes the native sidecar and the web container. In this example, the startupProbe delays nginx startup until the sidecar creates index.html; it is not an ongoing readiness check. A readinessProbe can be added when sidecar health should continue to affect Pod readiness.
Read the content nginx serves from the shared file:
kubectl exec web -n sidecar-lab -c web -- wget -qO- http://localhost/Sample output:
Sidecar updated at 2026-07-26T06:49:57ZConfirm both containers see the same volume through different mount paths. Write a fixed test file from the sidecar:
kubectl exec web -n sidecar-lab -c content-writer -- sh -c 'echo shared-volume-test > /shared/check.txt'Read it through the nginx container's mount path:
kubectl exec web -n sidecar-lab -c web -- cat /usr/share/nginx/html/check.txtSample output:
shared-volume-testThe file written at /shared/check.txt in the sidecar appears at /usr/share/nginx/html/check.txt in nginx because both paths mount the same shared volume.
View logs from individual containers
Kubernetes needs a container name when a Pod runs more than one container:
kubectl logs web -n sidecar-lab -c webSample output:
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.shThe sidecar writes each update to stdout through tee:
kubectl logs web -n sidecar-lab -c content-writer --tail=1Sample output:
Sidecar updated at 2026-07-26T06:49:57ZThe -c flag selects one container. Use --all-containers=true when you want logs from every container in the Pod, including restartable init containers such as native sidecars:
kubectl logs web -n sidecar-lab --all-containers=true --prefixSample output:
[pod/web/content-writer] Sidecar updated at 2026-07-26T06:57:42Z
[pod/web/web] 2026/07/26 06:57:43 [notice] 1#1: start worker processes
[pod/web/web] 2026/07/26 06:57:43 [notice] 1#1: start worker process 30--prefix adds the Pod and container name to each line so you can distinguish nginx output from sidecar output.
For container state, mounts, and Events:
kubectl describe pod web -n sidecar-labNative Sidecars vs Regular Helper Containers
This lab uses the current native sidecar form under spec.initContainers with restartPolicy: Always. Regular helper containers remain valid when startup ordering and finite-workload completion behaviour do not matter.
In the traditional pattern, the application and helper are both declared under spec.containers:
spec:
containers:
- name: web
image: nginx:1.27-alpine
- name: log-agent
image: busybox:1.36
command: ['sh', '-c', 'sleep infinity']Kubernetes starts them without a guaranteed order, and both follow the Pod-level restart policy. Kubernetes still supports ordinary multi-container Pods, but they are not marked as native sidecars.
| Concern | Regular helper (spec.containers) |
Native sidecar (initContainers + restartPolicy: Always) |
|---|---|---|
| Startup order | No guaranteed order | Starts before application containers |
| Restart policy | Pod-level | Container-level Always |
| Probes during Pod lifetime | Same as any app container | Supported on the sidecar |
| Job completion | Long-running helper can block Pod completion | Stopped after application containers finish |
For sequential init containers that must complete before the application runs, see Kubernetes init containers — that workflow is separate from native sidecars.
Kubernetes Adapter Pattern
An adapter container transforms application output into a format another system expects.
Example flow:
Application → shared volume → adapter → standard outputTypical shape:
- Application container writes logs or metrics in its own format to a shared volume
- Adapter container reads those files from a different mount path
- Adapter emits JSON lines, Prometheus text, or another standard format
spec:
containers:
- name: application
image: app-image:1.0
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: log-adapter
image: adapter-image:1.0
args:
- --input=/input/app.log
- --output-format=json
volumeMounts:
- name: logs
mountPath: /input
volumes:
- name: logs
emptyDir: {}This abbreviated pattern assumes an adapter image that reads the application log and performs proper JSON encoding; adapter-image:1.0 is a placeholder rather than a runnable public image.
You can adapt the sidecar lab by replacing content-writer with a container that reads /shared/app.log and emits JSON lines — the volume mount pattern stays the same.
Kubernetes Ambassador Pattern
An ambassador acts as a local proxy between the application and a remote service.
Example flow:
Application → localhost → ambassador → external serviceTypical shape:
- Application connects to
localhost:8080and treats the ambassador as the real service - Ambassador container listens on that port inside the shared Pod network namespace
- Ambassador forwards traffic to an external database, cache, or API
The following is an abbreviated Pod-template fragment. Replace proxy-image:1.0 and its environment variables with the configuration supported by the proxy you choose.
spec:
containers:
- name: application
image: app-image:1.0
env:
- name: DB_HOST
value: localhost
- name: DB_PORT
value: "8080"
- name: ambassador
image: proxy-image:1.0
ports:
- containerPort: 8080
env:
- name: UPSTREAM
value: postgres.example.svc.cluster.local:5432The application stays unaware of the remote address. This article does not cover service mesh, Ingress, or full proxy configuration — only the Pod-local pattern.
Sidecar vs Adapter vs Ambassador
| Pattern | Main purpose | Typical communication | Example |
|---|---|---|---|
| Sidecar | Extend or support the application | Shared volume or localhost | nginx + content-writer in this lab |
| Adapter | Transform application output | Shared volume or local stream | App writes plain logs; adapter emits JSON |
| Ambassador | Proxy external communication | Localhost network connection | App uses localhost:8080; ambassador reaches a remote API |
Adapter and ambassador are specialised multi-container shapes. In practice they are often described as sidecar patterns because the helper runs beside the main container in the same Pod.
Container Startup and Failure Behaviour
Regular containers under spec.containers do not have guaranteed startup ordering. Do not assume a sidecar is ready the moment the main container starts — use retries, probes, or a native sidecar when ordering matters.
- Regular application containers follow the Pod-level
restartPolicy - Native sidecars use container-level
restartPolicy: Alwaysand restart independently - All containers remain scheduled on the same node
- Native sidecars are stopped after the regular application containers terminate
| Scenario | What happens |
|---|---|
| App container crashes | Kubelet restarts it inside the same Pod name when restartPolicy is Always |
| Native sidecar crashes | Sidecar restarts independently; app container keeps running |
Helper under spec.containers in a Job |
Long-running loop can keep the Pod from completing |
| Native sidecar in a Job | Sidecar stops after the main container finishes; Job can complete |
For guaranteed preparation before application containers start, use Kubernetes init containers.
Resource Requests in Multi-Container Pods
Each container can define its own requests and limits:
- The scheduler considers the combined requirements of the entire Pod
- A supporting container still consumes CPU and memory on the node
- An unhealthy or resource-starved sidecar can affect the whole Pod
initContainers:
- name: content-writer
image: busybox:1.36
restartPolicy: Always
resources:
requests:
memory: 32Mi
cpu: 50m
containers:
- name: web
image: nginx:1.27-alpine
resources:
requests:
memory: 64Mi
cpu: 100mIn this example, the scheduler reserves 150m CPU and 96Mi of memory for the concurrently running application and native sidecar. When regular init containers are also present, Kubernetes calculates the effective Pod request by comparing this combined request with the effective init-container requirement.
Native-sidecar requests participate in the Pod's effective scheduling request differently from regular sequential init containers.
For requests, limits, and QoS class detail, see Kubernetes resource requests and limits.
When Should Containers Share a Pod?
| Situation | Recommended placement | Example |
|---|---|---|
| Log shipper must read files the app writes | Same Pod | nginx and Fluent Bit sharing a volume |
| Components scale at different rates | Separate Pods | Frontend and API Deployments |
Helper must proxy localhost traffic |
Same Pod | Application and ambassador |
| Stable Service endpoint for one tier | Separate Pods | API Deployment exposed through a Service |
| One-time migration before application startup | Same Pod, regular init container | Init container completes before the app starts |
Regular init containers execute in the same Pod before application containers.
Common Multi-Container Pod Problems
| Symptom | Likely cause | What to check |
|---|---|---|
kubectl logs asks for a container |
Pod has multiple containers | Add -c <name> or --all-containers=true |
| Shared file is missing | Containers do not mount the same volume | Compare volumes and both volumeMounts |
| Localhost connection is refused | Wrong port, startup race, or process stopped | Check listeners, container state, and probes |
| Sidecar repeatedly restarts | Its command exits or fails | Check sidecar logs, exit code, and restart count |
| Finite workload does not complete | Long-running helper is under spec.containers |
Use a native sidecar or redesign the helper lifecycle |
What's Next
References
- Sidecar Containers
- Pods
- Init Containers
- Volumes
- Liveness, Readiness, and Startup Probes
- kubectl logs
Summary
A Kubernetes multi-container Pod co-locates tightly coupled containers on one node and gives them a shared network namespace. Each container still has its own state and can restart independently. The native sidecar lab showed the current pattern: content-writer under initContainers with restartPolicy: Always updates index.html on a shared emptyDir volume while nginx serves it on localhost. A startupProbe gates application startup until the sidecar has written the first file.
Adapter and ambassador patterns specialise that idea — transform output on a shared volume, or proxy external traffic through localhost. Regular helpers live in spec.containers; native sidecars use initContainers with restartPolicy: Always when the helper must start first, keep running during the app lifetime, and stop after the application finishes.
Co-locate containers when they must share files or localhost and release together. Split them into separate Pods when scaling or lifecycle independence matters. When logs or files look wrong, check the container name flag, shared volume mounts, and which process owns each port before debugging the application itself.

