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. │
└─────────────────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode
📋 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 containersThe 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 │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode
🔧 Step-by-Step Guide
Step 1: Delete Existing Pod (if any)
kubectl delete pod webserver --ignore-not-found
Enter fullscreen mode Exit fullscreen mode
Step 2: Create the YAML Manifest
vi webserver.yaml
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
📖 YAML Breakdown
Volume Section
volumes:
- name: shared-logs
emptyDir: {}
Enter fullscreen mode Exit fullscreen mode
-
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
Enter fullscreen mode Exit fullscreen mode
-
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
Enter fullscreen mode Exit fullscreen mode
-
image: nginx:latest– Web server -
mountPath: /var/log/nginx– Where nginx writes logs
Step 3: Create the Pod
kubectl apply -f webserver.yaml
Enter fullscreen mode Exit fullscreen mode
Output:
pod/webserver created
Enter fullscreen mode Exit fullscreen mode
Step 4: Verify the Pod
kubectl get pods
Enter fullscreen mode Exit fullscreen mode
Output:
NAME READY STATUS RESTARTS AGE
webserver 1/1 Running 0 10s
Enter fullscreen mode Exit fullscreen mode
Step 5: Verify Both Containers
# Check init container exists
kubectl get pods webserver -o jsonpath='{.status.initContainerStatuses[*].name}'
Enter fullscreen mode Exit fullscreen mode
Output:
sidecar-container
Enter fullscreen mode Exit fullscreen mode
# Check regular container exists
kubectl get pods webserver -o jsonpath='{.status.containerStatuses[*].name}'
Enter fullscreen mode Exit fullscreen mode
Output:
nginx-container
Enter fullscreen mode Exit fullscreen mode
# Check images
kubectl get pods webserver -o jsonpath='{.spec.initContainers[*].image}'
Enter fullscreen mode Exit fullscreen mode
Output:
ubuntu:latest
Enter fullscreen mode Exit fullscreen mode
kubectl get pods webserver -o jsonpath='{.spec.containers[*].image}'
Enter fullscreen mode Exit fullscreen mode
Output:
nginx:latest
Enter fullscreen mode Exit fullscreen mode
Step 6: Generate Traffic
kubectl exec -it webserver -c nginx-container -- curl localhost
Enter fullscreen mode Exit fullscreen mode
Step 7: Check Sidecar Logs
kubectl logs webserver -c sidecar-container
Enter fullscreen mode Exit fullscreen mode
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" "-"
Enter fullscreen mode Exit fullscreen mode
Step 8: Verify Shared Volume
# Check in nginx container
kubectl exec -it webserver -c nginx-container -- ls -la /var/log/nginx/
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
# Check in sidecar container (same files!)
kubectl exec -it webserver -c sidecar-container -- ls -la /var/log/nginx/
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
🔍 How the Sidecar Command Works
while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done
Enter fullscreen mode Exit fullscreen mode
Part Purposewhile 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/
Enter fullscreen mode Exit fullscreen mode
🎯 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!
Enter fullscreen mode Exit fullscreen mode
- 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
Enter fullscreen mode Exit fullscreen mode
Useful Commands
Command Purposekubectl 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