00:00

Sidecar Pattern in Microservices

Picture a motorcycle with a sidecar attached. The motorcycle does the main job, driving, while the sidecar adds extra capabilities, such as carrying a passenger or luggage, without changing the motorcycle itself. The Sidecar pattern in microservices works exactly like this.

This guide explains how sidecars work, why they are useful, and how to run them in Kubernetes, with Docker Compose and in a service mesh. It also covers the Ambassador and Adapter variations, sidecar-less alternatives, and best practices, with configuration and Java examples.

1. What Is the Sidecar Pattern?

The Sidecar pattern attaches a helper component, the sidecar, to a main application, the microservice, to extend or enhance it without changing the application’s code.

In technical terms, the sidecar is a separate process or container that runs right next to the main application: in the same Kubernetes pod, or on the same virtual machine. The two share the same life cycle: they are created, scaled and removed together. If the service scales to five instances, there are five sidecars as well.

Motorcycle and sidecar
 +-------------------------- Pod / host ---------------------------+
 |                                                                  |
 |   [ Main service ]  <--- localhost --->  [ Sidecar ]             |
 |    business logic                        logging, metrics,       |
 |    (the motorcycle)                      security, proxying      |
 |                                          (the sidecar)           |
 +------------------------------------------------------------------+

2. Why Do We Need the Sidecar Pattern?

Every microservice needs the same supporting features: logging, metrics, security, configuration, retries. The Sidecar pattern moves these non-business responsibilities into a helper, so the service stays simple and focused:

ReasonWhat it means
Separation of concernsBusiness logic stays in the service; cross-cutting concerns such as logging, retries, security and metrics live in the sidecar
Consistency across servicesEvery service uses the same sidecar for monitoring or authentication, so behavior is the same everywhere
Language independenceThe sidecar works the same for Java, Python, Go or .NET services, so no library is needed per language
Easier maintenanceCommon features are updated in the sidecar image without changing or redeploying application code
Reliability and securitySidecars can add traffic control, retries, circuit breaking and encryption to services that lack them

3. How a Sidecar Works with Its Service

Because the main container and the sidecar run together in one unit, they share resources that make cooperation easy:

Shared resourceHow the sidecar uses itExample
Network (same localhost)They talk over localhost, without leaving the machineThe sidecar scrapes http://localhost:8080/actuator/prometheus
Volumes (shared folders)One writes files, the other reads themThe service writes logs to /var/log/app; the sidecar ships them
Life cycleStarted, scaled and stopped togetherScaling to 5 pods creates 5 sidecars
Resources (node CPU and memory)Both are scheduled on the same machineRequests and limits must cover both containers

Communication over localhost is very fast and needs no service discovery: the sidecar is always at the same address.

4. A Practical Example: Metrics, Discovery and Logs

Consider a service that must report metrics, register with service discovery and send its logs to a central store.

4.1 Without a sidecar

The microservice code contains client logic for the metrics system, the discovery system and the log store. Every service, in every language, needs its own version of this code. It bloats the service and makes upgrades painful.

4.2 With a sidecar

The main service contains only business logic. A separate sidecar container handles the infrastructure work, and they talk through localhost or a shared volume.

The main service…The sidecar…
Writes logs to standard output or a shared folderCollects the logs and ships them to a central store, such as Elasticsearch or Loki
Exposes a simple metrics endpointReads the metrics and forwards them to the monitoring system
Calls other services through a local addressHandles discovery, load balancing, retries and encryption for those calls
Reports its health on /actuator/healthRegisters or deregisters the service in the registry based on that health

5. A Sidecar in Kubernetes

In Kubernetes, a sidecar is simply a second container in the same pod. Here, a log shipper reads the order service’s log files from a shared emptyDir volume:

order-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        # 1. The main application
        - name: order-service
          image: shop/order-service:2.1
          ports:
            - containerPort: 8080
          env:
            - name: LOGGING_FILE_NAME          # Spring Boot: logging.file.name
              value: /var/log/app/order-service.log
          volumeMounts:
            - name: app-logs
              mountPath: /var/log/app
          resources:
            requests: { cpu: "500m", memory: "512Mi" }

        # 2. The sidecar: ships the log files to the central log store
        - name: log-shipper
          image: fluent/fluent-bit:3.1
          volumeMounts:
            - name: app-logs
              mountPath: /var/log/app
              readOnly: true
          resources:
            requests: { cpu: "50m", memory: "64Mi" }
            limits: { cpu: "100m", memory: "128Mi" }

      volumes:
        - name: app-logs
          emptyDir: {}          # shared folder that lives as long as the pod

Fluent Bit is configured, through its own configuration file, to read /var/log/app/*.log and send the entries to the log store. Scaling the deployment to 10 replicas automatically creates 10 log shippers, one per pod.

6. Native Sidecar Containers in Kubernetes

Ordinary sidecar containers start and stop at the same time as the main container, which can cause problems: the app may start before its proxy is ready, or the log shipper may stop before the last logs are sent. Recent Kubernetes versions offer native sidecar containers to fix this. A native sidecar is an init container with restartPolicy: Always:

Native sidecar (part of the pod spec)
spec:
  initContainers:
    - name: log-shipper
      image: fluent/fluent-bit:3.1
      restartPolicy: Always       # turns this init container into a native sidecar
      volumeMounts:
        - name: app-logs
          mountPath: /var/log/app
          readOnly: true
  containers:
    - name: order-service
      image: shop/order-service:2.1
      volumeMounts:
        - name: app-logs
          mountPath: /var/log/app
Classic sidecarNative sidecar
StartsAt the same time as the appBefore the app, and keeps running
StopsAt the same time as the appAfter the app has stopped
In JobsCan keep a finished Job’s pod running foreverStops automatically when the main container finishes

7. What Changes in the Java Application

A sidecar removes code from your service, but the service must still cooperate with it. For a Spring Boot service, that usually means a few small settings:

application.yml (order-service)
spring:
  application:
    name: order-service

# Write logs where the log-shipper sidecar can read them
logging:
  file:
    name: ${LOGGING_FILE_NAME:/var/log/app/order-service.log}

# Expose health and metrics for sidecars and the platform
management:
  endpoints:
    web:
      exposure:
        include: health,prometheus
  endpoint:
    health:
      probes:
        enabled: true

For metrics, add micrometer-registry-prometheus, and Spring Boot publishes everything at /actuator/prometheus, ready for a monitoring agent or sidecar to collect. The business code does not change at all.

Tip: In containers, writing logs to standard output is usually simpler than writing files: Kubernetes already collects them, and a node-level agent can ship them. A file-reading sidecar is useful when an application can only log to files, or needs a special format per service.

8. The Ambassador: A Sidecar for Outgoing Calls

The Ambassador pattern is a special kind of sidecar that acts as a local proxy for a service’s outgoing calls. The application calls the ambassador on localhost, and the ambassador handles the hard parts of talking to a remote system: TLS, authentication, retries, timeouts, circuit breaking and monitoring.

Example: the order service calls an external payment provider. Instead of building all that logic into the service, it calls an ambassador sidecar, such as an Envoy proxy configured for the provider:

PaymentProviderClient.java
package com.example.order.payment;

import java.math.BigDecimal;

import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

@Component
public class PaymentProviderClient {

    private final RestClient restClient;

    public PaymentProviderClient(RestClient.Builder builder) {
        // The ambassador sidecar listens on localhost:9000.
        // It adds TLS, the API key, retries and timeouts before
        // forwarding the call to https://api.payments.example
        this.restClient = builder.baseUrl("http://localhost:9000").build();
    }

    public ChargeResponse charge(String orderId, BigDecimal amount) {
        return restClient.post()
                .uri("/v1/charges")
                .header("Idempotency-Key", orderId)
                .body(new ChargeRequest(orderId, amount))
                .retrieve()
                .body(ChargeResponse.class);
    }

    public record ChargeRequest(String orderId, BigDecimal amount) {
    }

    public record ChargeResponse(String chargeId, String status) {
    }
}

The Java code stays simple and contains no secrets: the API key lives only in the ambassador’s configuration. Services in other languages can use the same ambassador image, and switching providers, or adding retries, changes only the sidecar.

9. Sidecars Without Kubernetes

Sidecars are most common in Kubernetes, but the idea works anywhere two processes can share a host. With Docker Compose, a container can join another container’s network, so both share the same localhost:

docker-compose.yml
services:
  order-service:
    image: shop/order-service:2.1
    ports:
      - "8080:8080"
    volumes:
      - app-logs:/var/log/app

  log-shipper:
    image: fluent/fluent-bit:3.1
    network_mode: "service:order-service"   # share order-service's localhost
    volumes:
      - app-logs:/var/log/app:ro
    depends_on:
      - order-service

volumes:
  app-logs:

On virtual machines, the same pattern appears as a helper process or agent installed next to the application, for example a monitoring agent or a local proxy managed by systemd.

10. Sidecars in a Service Mesh

The most famous use of sidecars is the service mesh. Tools such as Istio and Linkerd add a small proxy sidecar, such as Envoy or the Linkerd proxy, to every pod. All network traffic in and out of the service passes through that proxy, which adds:

  • Mutual TLS: encrypted, authenticated traffic between services.
  • Retries, timeouts and circuit breaking, configured centrally.
  • Load balancing and traffic splitting, such as canary releases.
  • Metrics and traces for every call, without code changes.

With Istio, you do not even add the sidecar yourself. Label a namespace, and Istio injects the proxy into every new pod:

Terminal
# Every pod created in the "shop" namespace now gets an Envoy sidecar
kubectl label namespace shop istio-injection=enabled

# Restart the deployment so its pods are recreated with the sidecar
kubectl rollout restart deployment/order-service -n shop
Retry policy applied by the sidecar proxies
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: payment-service
spec:
  hosts:
    - payment-service
  http:
    - route:
        - destination:
            host: payment-service
      timeout: 5s
      retries:
        attempts: 3
        perTryTimeout: 2s
        retryOn: 5xx,connect-failure

The order service keeps calling http://payment-service as before; the sidecars add timeouts and retries. Be careful not to retry in both the mesh and your Java code, or the attempts multiply. See the Retry pattern.

11. Common Use Cases

Use caseWhat the sidecar doesExample tools
Service mesh proxyHandles all service-to-service traffic: TLS, retries, load balancing, metricsEnvoy (Istio), Linkerd proxy
Log shippingReads logs from a shared volume and sends them to a central storeFluent Bit, Fluentd, Vector
Metrics and tracingCollects metrics and traces and forwards themOpenTelemetry Collector, Prometheus exporters
Configuration and secretsFetches configuration or secrets and keeps local files up to dateVault Agent, config reloaders
Authentication proxyChecks logins or tokens before requests reach the appOAuth2 Proxy
AmbassadorLocal proxy for outgoing calls to external systemsEnvoy, custom proxies
AdapterConverts the app’s output into a standard formatExporters that turn custom metrics into Prometheus format
ApproachWhat it isBest for
Sidecar (general)Any helper container running next to the main serviceAdding capabilities without code changes
AmbassadorA sidecar that proxies the service’s outgoing callsCalling external or complex remote systems
AdapterA sidecar that translates the service’s output into a standard interfaceMaking legacy or third-party apps fit your monitoring or logging standards
Shared libraryThe same feature built into each service as code, such as Resilience4j or MicrometerSingle-language systems wanting no extra containers
Node agent (DaemonSet)One helper per machine instead of per podLog and metrics collection at lower cost

Libraries and sidecars are not enemies. A Java team may use Resilience4j and Micrometer inside the service, and a mesh sidecar for encryption and traffic control. Choose based on how many languages you use and how much you want to keep out of application code.

13. Sidecar-less Alternatives

Sidecars cost CPU and memory in every pod. With hundreds of pods, that adds up. Newer approaches reduce this:

  • Node-level agents (DaemonSets): one log or metrics collector per machine instead of one per pod.
  • Istio ambient mode: a shared, per-node proxy handles encryption and basic traffic, with optional per-service proxies only where advanced features are needed.
  • eBPF-based networking, such as Cilium: some mesh features run inside the operating system kernel instead of a proxy.
  • Proxyless gRPC: gRPC clients receive mesh configuration directly, with no proxy in between.

Sidecars remain the most flexible and widely supported option, but it is worth checking these alternatives for large clusters.

14. Key Benefits

  • Decoupling and separation of concerns: the main service focuses purely on business logic, so it is simpler, cleaner and easier to test.
  • Reusability: the same sidecar works with many services in different languages; the infrastructure logic is written once.
  • Polyglot support: a Python service and a .NET service use exactly the same sidecar for discovery or metrics, with no client libraries to maintain per language.
  • Independent updates: upgrade the sidecar image, for example for a security fix, without touching application code.
  • Isolation: the sidecar runs as a separate process, with its own resource limits, so a bug in it does not have to crash the main service, and vice versa.

15. Challenges and Considerations

ChallengeWhy it mattersHow to handle it
ComplexityTwo or more components to deploy, configure and monitor per serviceUse standard sidecar images and automatic injection
LatencyProxy sidecars add a local hop to every request (usually well under a millisecond, but it adds up)Measure it; avoid unnecessary proxy chains
Resource usageEvery sidecar uses CPU and memory in every podSet requests and limits; consider node agents or sidecar-less options
Tied life cycleSidecars scale with the service, even if they do not need toUse node-level agents for work that does not need one copy per pod
Startup and shutdown orderThe app may start before its proxy is ready, or the sidecar may stop too earlyUse native sidecar containers
DebuggingProblems can be in the app, the sidecar, or between themCheck both containers’ logs; use the mesh’s tracing

16. Best Practices

  1. Keep sidecars generic: no business logic, only reusable infrastructure features.
  2. Set resource requests and limits for every sidecar, so they cannot starve the main container.
  3. Use native sidecar containers where available, to get correct startup and shutdown order.
  4. Standardize sidecar images and versions across services, and update them centrally.
  5. Prefer automatic injection, as in a service mesh, over copying sidecar definitions into every deployment.
  6. Avoid duplicate features: do not retry or encrypt both in the sidecar and in the application.
  7. Monitor sidecars like any other component: health, resource use and errors.
  8. Question each sidecar: if a node agent or a small library would do the job, it may be cheaper.

17. Common Mistakes to Avoid

MistakeWhat goes wrongBetter approach
Putting business logic in a sidecarLogic is split across two deployables and becomes hard to changeKeep business logic in the service
No resource limits on sidecarsA busy sidecar slows down or starves the main applicationSet requests and limits
Retrying in both the mesh and the codeRetries multiply and overload struggling servicesChoose one place to retry
Ignoring startup orderThe app fails its first calls because the proxy is not ready yetUse native sidecars or wait for the proxy
One sidecar per small taskPods fill up with many helpers that each use resourcesCombine tasks, or use node-level agents
Unpinned sidecar versionsAn automatic update changes behavior in every service at oncePin versions and roll out updates gradually

18. Summary

The Sidecar pattern extends and enhances microservices without changing their code. A helper container runs next to each service, shares its network, storage and life cycle, and takes over cross-cutting concerns such as logging, metrics, configuration, security and traffic control. Variations such as the Ambassador, for outgoing calls, and the Adapter, for translating output, solve specific problems, and the service mesh applies the pattern to every service at once. Keep sidecars generic, give them resource limits, use native sidecar containers for correct start and stop order, and weigh them against node agents and libraries. Used well, sidecars let developers focus on business value while delegating complex operational concerns to reusable, standardized components. Continue with the API Gateway pattern to see how traffic enters the system before it reaches your services.