We have a web server container running the nginx image. The access and error logs generated by the web server are not critical enough to be placed on a persistent volume. However, Nautilus developers need access to the last 24 hours of logs so that they can trace issues and bugs. Therefore, we need to ship the access and error logs for the web server to a log-aggregation service. Following the separation of concerns principle, we implement the Sidecar pattern by deploying a second container that ships the error and access logs from nginx. Nginx does one thing, and it does it well - serving web pages. The second container also specializes in its task - shipping logs. Since containers are running on the same Pod, we can use a shared emptyDir volume to read and write logs.
- Create a pod named
webserver. - Create an
emptyDirvolume namedshared-logs. - Create a regular container in the
webserverpod from thenginx:latestimage namednginx-container, and an init container from theubuntu:latestimage namedsidecar-container. - Add the following command to the
sidecar-container"sh","-c","while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done" - Mount the
shared-logsvolume in both containers at/var/log/nginx. Ensure all containers are in a running state.
What is a Sidecar Container?
Think of a sidecar like a motorcycle sidecar – it's attached to the main vehicle and extends its capabilities without changing the main vehicle itself.
┌─────────────────────────────────────────────────────────────────────────────┐
│ The Sidecar Analogy │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Motorcycle: The Main Vehicle │ │
│ │ - Does its primary job (serving web pages) │ │
│ │ - Doesn't worry about extra tasks │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Sidecar: Adds Extra Functionality │ │
│ │ - Extends capabilities │ │
│ │ - Doesn't change the motorcycle │ │
│ │ - Can be attached/removed │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │
│ In Kubernetes, the "motorcycle" is your main container, │
│ and the "sidecar" is an additional container that enhances it. │
└─────────────────────────────────────────────────────────────────────────────┘
📋 What We'll Build Today
Our Task:
-
Pod Name:
webserver -
Main Container:
nginx-container(nginx:latest) – serves web pages -
Sidecar Container:
sidecar-container(ubuntu:latest) – ships logs -
Shared Volume:
shared-logs(emptyDir) -
Mount Path:
/var/log/nginx(both containers)
Why Sidecar?
Instead of modifying Nginx to handle log shipping, we add a separate container that specializes in this task. This follows the Separation of Concerns principle.
📖 Understanding the Sidecar Pattern
The Problem
Nginx generates logs, but we need to ship them to a log aggregator. Two approaches:
| Approach | Description | Pros | Cons |
|---|---|---|---|
| Modify Nginx | Add log shipping code to Nginx | Simple | Violates single responsibility |
| Sidecar Pattern | Add separate log shipping container | Clean separation, easy to update | More containers |
The Sidecar Solution
┌─────────────────────────────────────────────────────────────────────────────┐
│ Sidecar Pattern Architecture │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Pod: webserver │ │
│ │ │ │
│ │ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │ │
│ │ │ Main Container │ │ Sidecar Container │ │ │
│ │ │ nginx-container │ │ sidecar-container │ │ │
│ │ │ │ │ (Init Container) │ │ │
│ │ │ - Serves web pages │ │ restartPolicy: Always │ │ │
│ │ │ - Writes logs to shared │ │ - Reads logs │ │ │
│ │ │ volume │ │ - Ships logs to aggregator │ │ │
│ │ │ │ │ - Does NOT modify logs │ │ │
│ │ │ │ │ │ │ │
│ │ │ Writes to: │ │ Reads from: │ │ │
│ │ │ /var/log/nginx/access.log │◄─┤ /var/log/nginx/access.log │ │ │
│ │ │ /var/log/nginx/error.log │◄─┤ /var/log/nginx/error.log │ │ │
│ │ └──────────────────────────────┘ └──────────────────────────────┘ │ │
│ │ ▲ ▲ │ │
│ │ │ │ │ │
│ │ ┌─────────────────┴──────────────────────┴──────────────────────┐ │ │
│ │ │ Volume: shared-logs (emptyDir) │ │ │
│ │ │ Mounted at: /var/log/nginx │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
🔧 Step-by-Step Guide
Step 1: Delete Existing Pod (if any)
kubectl delete pod webserver --ignore-not-found
Step 2: Create the YAML Manifest
vi webserver.yaml
YAML Content:
apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
volumes:
- name: shared-logs
emptyDir: {}
initContainers:
- name: sidecar-container
image: ubuntu:latest
restartPolicy: Always
command: ["/bin/sh", "-c"]
args: ["while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
containers:
- name: nginx-container
image: nginx:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
📖 YAML Breakdown
Volume Section
volumes:
- name: shared-logs
emptyDir: {}
-
name: shared-logs– The volume name -
emptyDir: {}– Temporary storage that exists for the Pod's lifetime
Sidecar Container (Init Container with restartPolicy: Always)
initContainers:
- name: sidecar-container
image: ubuntu:latest
restartPolicy: Always # ← KEY: Makes it a sidecar!
command: ["/bin/sh", "-c"]
args: ["while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
-
restartPolicy: Always– This is what makes it a sidecar container! -
image: ubuntu:latest– Lightweight Linux image -
command– Infinite loop reading logs every 30 seconds -
mountPath: /var/log/nginx– Same mount path as nginx
Main Container (Nginx)
containers:
- name: nginx-container
image: nginx:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
-
image: nginx:latest– Web server -
mountPath: /var/log/nginx– Where nginx writes logs
Step 3: Create the Pod
kubectl apply -f webserver.yaml
Output:
pod/webserver created
Step 4: Verify the Pod
kubectl get pods
Output:
NAME READY STATUS RESTARTS AGE
webserver 1/1 Running 0 10s
Step 5: Verify Both Containers
# Check init container exists
kubectl get pods webserver -o jsonpath='{.status.initContainerStatuses[*].name}'
Output:
sidecar-container
# Check regular container exists
kubectl get pods webserver -o jsonpath='{.status.containerStatuses[*].name}'
Output:
nginx-container
# Check images
kubectl get pods webserver -o jsonpath='{.spec.initContainers[*].image}'
Output:
ubuntu:latest
kubectl get pods webserver -o jsonpath='{.spec.containers[*].image}'
Output:
nginx:latest
Step 6: Generate Traffic
kubectl exec -it webserver -c nginx-container -- curl localhost
Step 7: Check Sidecar Logs
kubectl logs webserver -c sidecar-container
Output:
2026/08/24 06:38:27 [notice] 1#1: using the "epoll" event method
2026/08/24 06:38:27 [notice] 1#1: nginx/1.31.4
...
::1 - - [24/Aug/2026:06:38:40 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.14.1" "-"
Step 8: Verify Shared Volume
# Check in nginx container
kubectl exec -it webserver -c nginx-container -- ls -la /var/log/nginx/
Output:
total 20
drwxrwxrwx 2 root root 4096 Aug 24 06:38 .
drwxr-xr-x 1 root root 4096 Aug 19 19:08 ..
-rw-r--r-- 1 root root 168 Aug 24 06:40 access.log
-rw-r--r-- 1 root root 1077 Aug 24 06:38 error.log
# Check in sidecar container (same files!)
kubectl exec -it webserver -c sidecar-container -- ls -la /var/log/nginx/
Output:
total 16
drwxrwxrwx 2 root root 4096 Aug 24 06:38 .
drwxr-xr-x 1 root root 4096 Aug 24 06:38 ..
-rw-r--r-- 1 root root 168 Aug 24 06:40 access.log
-rw-r--r-- 1 root root 1077 Aug 24 06:38 error.log
🔍 How the Sidecar Command Works
while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done
| Part | Purpose |
|---|---|
while true; do |
Infinite loop |
cat /var/log/nginx/access.log /var/log/nginx/error.log |
Read both log files |
sleep 30 |
Wait 30 seconds before repeating |
done |
End of loop |
What this achieves:
- ✅ Continuously reads logs every 30 seconds
- ✅ Outputs logs to stdout (for log aggregation)
- ✅ Keeps the container running
- ✅ No modifications to Nginx
📝 Complete Commands Summary
# 1. Delete existing pod
kubectl delete pod webserver --ignore-not-found
# 2. Create the YAML file
cat > webserver.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
volumes:
- name: shared-logs
emptyDir: {}
initContainers:
- name: sidecar-container
image: ubuntu:latest
restartPolicy: Always
command: ["/bin/sh", "-c"]
args: ["while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
containers:
- name: nginx-container
image: nginx:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
EOF
# 3. Apply the configuration
kubectl apply -f webserver.yaml
# 4. Check pod status
kubectl get pods
# 5. Wait for pod to be ready
kubectl wait --for=condition=ready pod/webserver --timeout=60s
# 6. Verify both containers exist
kubectl get pods webserver -o jsonpath='{.status.initContainerStatuses[*].name}'
kubectl get pods webserver -o jsonpath='{.status.containerStatuses[*].name}'
# 7. Verify images
kubectl get pods webserver -o jsonpath='{.spec.initContainers[*].image}'
kubectl get pods webserver -o jsonpath='{.spec.containers[*].image}'
# 8. Generate traffic
kubectl exec -it webserver -c nginx-container -- curl localhost
# 9. Check sidecar logs
kubectl logs webserver -c sidecar-container
# 10. Verify shared volume
kubectl exec -it webserver -c nginx-container -- ls -la /var/log/nginx/
kubectl exec -it webserver -c sidecar-container -- ls -la /var/log/nginx/
🎯 Key Learnings
1. Sidecar Pattern
- Extends the main container's functionality without modifying it
- Follows the Separation of Concerns principle
- Nginx serves web pages, sidecar ships logs
2. Shared Volume
-
emptyDirvolume is created at the Pod level - Both containers mount the same volume at the same path (
/var/log/nginx) - Nginx writes logs, sidecar reads them
3. Sidecar as Init Container with restartPolicy: Always
initContainers:
- name: sidecar-container
image: ubuntu:latest
restartPolicy: Always # ← Makes it a sidecar!
- Starts before the main container
- Continues running for the entire Pod lifetime
- Shares the same volume with the main container
4. Verification
-
kubectl logswith-cflag to check specific container logs -
kubectl execto verify shared volume contents
📊 Sidecar Use Cases
| Use Case | Main Container | Sidecar Container |
|---|---|---|
| Log Shipping | Web server (Nginx) | Log shipper (Fluentd) |
| Configuration Sync | App server | Config sync agent |
| Proxying | App server | Reverse proxy |
| Monitoring | App server | Monitoring agent |
| Database Migration | App server | Migration runner |
🎉 You Did It!
You've successfully implemented the Sidecar pattern in Kubernetes! This is a critical skill for:
- ✅ Log aggregation – Shipping logs to centralized storage
- ✅ Monitoring – Adding monitoring agents
- ✅ Proxying – Adding reverse proxies
- ✅ Configuration – Dynamic configuration updates
📚 Quick Reference
Pod with Sidecar Template
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
volumes:
- name: shared-data
emptyDir: {}
initContainers:
- name: sidecar
image: sidecar-image:latest
restartPolicy: Always # ← Makes it a sidecar!
volumeMounts:
- name: shared-data
mountPath: /data
containers:
- name: main-container
image: main-image:latest
volumeMounts:
- name: shared-data
mountPath: /data
Useful Commands
| Command | Purpose |
|---|---|
kubectl get pods |
List pods |
kubectl logs <pod> -c <container> |
View container logs |
kubectl describe pod <pod> |
Detailed pod info |
kubectl exec -it <pod> -c <container> -- /bin/bash |
Shell into container |
Top comments (0)