Skip to content
SP StackPractices
intermediate

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.

Topics: design

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

VariantDescriptionUse Case
Ambassador SidecarProxy for external service communicationService discovery, TLS termination
Adapter SidecarNormalize output from the main appLogging formats, metrics scraping
Config SidecarReload configuration dynamicallyConsul, etcd watchers
Security SidecarAuthentication/authorization proxyOAuth, 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.