A sidecar container in ECS is a second container within the same task definition that runs alongside your main application container, sharing its network namespace and often its storage volumes.

Imagine you have a web application running in an ECS task. You want to add a proxy like Nginx to handle TLS termination and route requests, or a logging agent like Fluentd to collect logs from your application. Instead of building these capabilities directly into your app container, you can deploy them as separate sidecar containers within the same ECS task.

Here’s an example task definition snippet:

{
  "family": "my-app-with-sidecar",
  "containerDefinitions": [
    {
      "name": "my-app",
      "image": "nginx:latest", // Or your actual app image
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 80
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/my-app",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "app"
        }
      }
    },
    {
      "name": "nginx-proxy",
      "image": "nginx:latest",
      "portMappings": [
        {
          "containerPort": 8080,
          "hostPort": 8080
        }
      ],
      "dependsOn": [
        {
          "condition": "START",
          "containerName": "my-app"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/my-app",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "proxy"
        }
      }
    }
  ],
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "cpu": "1024",
  "memory": "2048"
}

In this example, my-app is your primary application, and nginx-proxy is the sidecar. The dependsOn section ensures the my-app container starts before the nginx-proxy. Both containers share the same network namespace, meaning they can communicate with each other using localhost or container names if using awsvpc network mode.

The primary benefit is separation of concerns. Your application container only needs to worry about its core logic. The proxy handles network concerns like TLS, request routing, or load balancing. The logging sidecar handles log collection and forwarding, decoupling that from your application’s execution. This makes your application code cleaner, easier to test, and more portable. You can swap out the proxy or logging agent without touching your application’s code.

When using the awsvpc network mode, which is standard for Fargate and recommended for EC2, containers within a task share the same ENI and IP address. This means they can communicate with each other using localhost on their respective container ports. For instance, the nginx-proxy could be configured to forward traffic to localhost:80 (where my-app is listening).

If you’re using the older bridge network mode on EC2, containers are on a private network managed by Docker. They can communicate using their container names as hostnames. For example, the proxy could send requests to my-app:80. However, awsvpc is generally preferred for its improved security and network isolation.

The dependsOn directive is crucial for managing startup order. Without it, the proxy might try to send traffic to your application before it’s ready, leading to errors. The START condition ensures the dependent container has successfully started. Other conditions like HEALTHY (if you have health checks configured) or COMPLETE (for jobs) are also available.

A common pattern is to use a shared tmpfs volume for logs if your application writes logs to files. The application container writes to a file in the shared volume, and the logging sidecar tail’s that file.

// ... within containerDefinitions for both app and logging sidecar
"volumesFrom": [], // Not needed if using awsvpc and localhost
"mountPoints": [
  {
    "sourceVolume": "shared-logs",
    "containerPath": "/var/log/app"
  }
],
// ...
// ... at the task definition level
"volumes": [
  {
    "name": "shared-logs",
    "host": { // For EC2 launch type
      "sourcePath": "/var/lib/ecs/shared-logs" // Example path
    },
    "dockerVolumeConfiguration": { // For Fargate, or if using custom Docker volumes on EC2
      "scope": "shared",
      "autoprovision": true
    }
  }
]

Note that for Fargate, host volumes aren’t applicable. You’d typically rely on awsvpc networking and inter-container communication via localhost, or use external log aggregation services. For file-based logging sharing on Fargate, you’d generally use a shared tmpfs volume mounted to both containers, where the application writes logs and the sidecar reads them.

The ability to share network namespaces is what makes sidecars powerful. Your main application container doesn’t need to be aware of the proxy; it just binds to localhost or a designated port. The sidecar intercepts or augments traffic.

When you define portMappings for both containers in the same task, those containerPort values are unique within that task’s network namespace. If your application listens on 80 and your proxy also needs to listen on 80 (e.g., for external traffic), you might map the proxy’s container port 80 to a different hostPort (if on EC2) or ensure the proxy is the one exposing the 80 port to the outside world while forwarding to localhost:80 of the app container.

The next step is often integrating these sidecars with service discovery or configuring more complex routing rules within the proxy.

Want structured learning?

Take the full Ecs course →