AWS Cloud Map lets your ECS services find each other without hardcoding IP addresses.

Imagine you have two ECS services, frontend and backend. The frontend service needs to talk to the backend service. Traditionally, you’d hardcode the backend’s IP address or a load balancer DNS name into the frontend’s configuration. This is brittle; if the backend service scales up or down, its IPs change, and your frontend breaks. Cloud Map solves this by acting like a dynamic DNS registry. When a backend task starts, Cloud Map registers its IP and port. When the frontend needs to talk to backend, it queries Cloud Map for the current backend task IPs.

Here’s how it looks in action.

First, we create a Cloud Map namespace. This is like a logical grouping for your service discoveries.

aws servicediscovery create-private-dns-namespace \
  --name my-ecs-namespace \
  --vpc-id vpc-0123456789abcdef0 \
  --region us-east-1

The output will give you a NamespaceId. Keep that handy.

Next, we associate this namespace with our ECS cluster. This allows ECS to automatically discover and register services within that cluster.

aws servicediscovery associate-vpc-with-service-discovery \
  --service-discovery-id <your-namespace-id> \
  --vpc-id vpc-0123456789abcdef0 \
  --region us-east-1

Now, let’s define an ECS service, say backend, and configure it to register with Cloud Map. In your ECS service definition (either in run-task or a task definition for a service), you’ll add a serviceDiscoveryConfig block.

For a service that uses a Load Balancer (ALB/NLB), you’d typically register the Load Balancer’s DNS name.

{
  "taskDefinition": "my-backend-task-def",
  "cluster": "my-ecs-cluster",
  "serviceName": "backend",
  "desiredCount": 2,
  "loadBalancers": [
    {
      "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-backend-tg/abcdef0123456789",
      "containerName": "backend-container",
      "containerPort": 8080
    }
  ],
  "serviceRegistries": [
    {
      "registryId": "<your-namespace-id>",
      "namespaceId": "<your-namespace-id>",
      "serviceName": "backend.my-ecs-namespace.my-vpc.local",
      "containerName": "backend-container",
      "containerPort": 8080,
      "type": "DNS_HTTP"
    }
  ]
}

If your service doesn’t use a load balancer and you want to register individual task IPs, you’d use DNS_PRIVATE or DNS_PUBLIC and specify ipAddressType.

{
  "taskDefinition": "my-backend-task-def",
  "cluster": "my-ecs-cluster",
  "serviceName": "backend",
  "desiredCount": 2,
  "serviceRegistries": [
    {
      "registryId": "<your-namespace-id>",
      "namespaceId": "<your-namespace-id>",
      "serviceName": "backend.my-ecs-namespace.my-vpc.local",
      "containerName": "backend-container",
      "containerPort": 8080,
      "type": "DNS_PRIVATE"
    }
  ]
}

When ECS starts tasks for this backend service, it will automatically call Cloud Map APIs to register each task’s IP address and port against the backend.my-ecs-namespace.my-vpc.local DNS name.

Now, for the frontend service to discover backend, it needs to be configured to query Cloud Map. This is done by setting environment variables in the frontend service’s task definition.

{
  "name": "BACKEND_SERVICE_DISCOVERY",
  "value": "backend.my-ecs-namespace.my-vpc.local"
}

Your frontend application can then use a Cloud Map client library or simply perform a DNS lookup for backend.my-ecs-namespace.my-vpc.local. The DNS records returned will be the IP addresses of the currently running backend tasks.

The type field in serviceRegistries is crucial. DNS_HTTP is used when registering a load balancer’s DNS name. Cloud Map periodically polls the health check endpoint of the load balancer (or the target group health checks) to determine which registered targets are healthy. DNS_PRIVATE or DNS_PUBLIC are for registering task IPs directly. In this case, Cloud Map relies on ECS to report task health. When a task is unhealthy, its IP is removed from the DNS records.

The serviceName in the serviceRegistries block is the FQDN that other services will use to discover this service. It follows the pattern service-name.namespace-name.aws-local. For private DNS namespaces, the aws-local part might be omitted or replaced with your VPC’s domain name depending on how you configured the namespace. You can verify this FQDN by looking at the Cloud Map service details.

The magic happens because Cloud Map integrates directly with ECS. When ECS launches a task that has a serviceRegistry configured, it automatically performs the registration. When the task stops, ECS automatically deregisters it. This seamless integration means you don’t need separate automation to keep your service registry up-to-date.

The registryId and namespaceId are often the same value, which is the ARN of the Cloud Map namespace. When you create a namespace, you get a NamespaceId. You use this NamespaceId to associate your VPC and then reference it when defining your ECS service’s serviceRegistries.

One common point of confusion is how to specify the serviceName in the serviceRegistries block. While you can specify a custom DNS name, it’s generally recommended to use the auto-generated name provided by Cloud Map for private DNS namespaces, which is often in the format service-name.namespace-name.aws-local. You can find the correct FQDN by navigating to your Cloud Map namespace in the AWS console and looking at the "Services" tab. Clicking on your service there will show you the FQDN.

Once your frontend service is configured to use Cloud Map, the next step is usually to implement robust client-side retry logic in your frontend application to handle transient network issues or brief periods where a backend service might be temporarily unavailable during scaling events.

Want structured learning?

Take the full Ecs course →