Sidecar Pattern
Deploy helper components alongside the main application as separate containers or processes. A microservices pattern for extending functionality without modifying the main app.
Sidecar Pattern
Overview
The Sidecar Pattern deploys helper components alongside the main application as separate containers or processes. These sidecars extend or enhance the main application with cross-cutting concerns like logging, monitoring, configuration, or networking — without modifying the main application’s code.
When to Use
Use the Sidecar Pattern when:
- You need to add cross-cutting concerns (logging, metrics, config reloading) without changing the main app
- The main application is a third-party service you cannot modify
- Components need to share a lifecycle but be independently deployable and scalable
- You want to isolate failure domains — a sidecar crash shouldn’t bring down the main app
- Examples: Istio proxy, Fluent Bit log shipper, Vault agent for secrets, NGINX alongside app server
Solution
Python
import threading
import time
class MainApp:
def __init__(self):
self.running = True
self.log_buffer = []
def do_work(self):
while self.running:
self.log_buffer.append(f"Processed at {time.time()}")
time.sleep(1)
def get_logs(self):
return self.log_buffer
# Sidecar: log shipper
class LogShipperSidecar:
def __init__(self, app: MainApp, interval: int = 5):
self.app = app
self.interval = interval
self.thread = None
def start(self):
self.thread = threading.Thread(target=self._ship, daemon=True)
self.thread.start()
def _ship(self):
while self.app.running:
time.sleep(self.interval)
logs = self.app.get_logs()
if logs:
print(f"[Sidecar] Shipping {len(logs)} logs to external service")
logs.clear()
# Usage
app = MainApp()
shipper = LogShipperSidecar(app)
shipper.start()
# Simulate main app work
for _ in range(12):
app.do_work()
JavaScript
class MainApp {
constructor() {
this.running = true;
this.logBuffer = [];
}
doWork() {
this.logBuffer.push(`Processed at ${Date.now()}`);
}
getLogs() {
return this.logBuffer;
}
}
// Sidecar: metrics collector
class MetricsSidecar {
constructor(app, intervalMs = 5000) {
this.app = app;
this.interval = intervalMs;
}
start() {
this.timer = setInterval(() => {
if (!this.app.running) return;
const logs = this.app.getLogs();
console.log(`[Sidecar] Collected ${logs.length} events for metrics`);
}, this.interval);
}
stop() {
clearInterval(this.timer);
}
}
// Usage
const app = new MainApp();
const metrics = new MetricsSidecar(app);
metrics.start();
// Simulate work
const work = setInterval(() => app.doWork(), 1000);
setTimeout(() => {
clearInterval(work);
metrics.stop();
}, 12000);
Java
import java.util.*;
import java.util.concurrent.*;
class MainApp {
volatile boolean running = true;
List<String> logBuffer = Collections.synchronizedList(new ArrayList<>());
void doWork() {
logBuffer.add("Processed at " + System.currentTimeMillis());
}
List<String> getLogs() {
return new ArrayList<>(logBuffer);
}
}
// Sidecar: health reporter
class HealthSidecar {
private final MainApp app;
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
HealthSidecar(MainApp app, int intervalSec) {
this.app = app;
scheduler.scheduleAtFixedRate(() -> {
if (!app.running) return;
System.out.println("[Sidecar] Reporting health: " + app.getLogs().size() + " events processed");
}, intervalSec, intervalSec, TimeUnit.SECONDS);
}
void stop() {
scheduler.shutdown();
}
}
// Usage
MainApp app = new MainApp();
HealthSidecar sidecar = new HealthSidecar(app, 5);
for (int i = 0; i < 12; i++) {
app.doWork();
Thread.sleep(1000);
}
sidecar.stop();
Explanation
The Sidecar Pattern separates concerns into co-located but independent processes:
- Main Container: The core application focused on business logic
- Sidecar Container: A helper process that shares the pod/vm/host but runs independently
- Shared Resources: Filesystem, network namespace, localhost — allowing communication without external networking
- Independent Lifecycle: Sidecars can be updated, scaled, or restarted independently of the main app
Variants
| Variant | Description | Use Case |
|---|---|---|
| Ambassador Sidecar | Proxy for external service communication | Service discovery, TLS termination |
| Adapter Sidecar | Normalize output from the main app | Logging formats, metrics scraping |
| Config Sidecar | Reload configuration dynamically | Consul, etcd watchers |
| Security Sidecar | Authentication/authorization proxy | OAuth, mTLS, Vault |
Best Practices
- Keep sidecars lightweight — they should use minimal CPU/memory
- Share a localhost interface for inter-process communication instead of external networking
- Monitor sidecar health independently — use separate health checks
- Version sidecars independently from the main application
- Document sidecar resource requirements for capacity planning
- Use Kubernetes init containers for one-time setup sidecars
Common Mistakes
- Making sidecars too heavy, consuming resources the main app needs
- Tight coupling between sidecar and main app, defeating the separation purpose
- Not handling sidecar failures gracefully — the main app should degrade, not crash
- Adding too many sidecars per pod, creating operational complexity
- Using sidecars when simple in-process middleware would suffice
Frequently Asked Questions
Q: What is the difference between Sidecar and Ambassador? A: Ambassador is a specific type of sidecar that proxies external service communication. Sidecar is the general pattern — any auxiliary container counts (logging, config, monitoring, proxy).
Q: Should I use a sidecar or add the functionality in-process? A: Use a sidecar when the functionality is language-agnostic, needs independent deployment, or when you cannot modify the main app. Use in-process when latency is critical and the logic is simple.