Search code examples
amazon-web-servicesdockerdocker-composeamazon-ecsamazon-route53

Assigning subdomain to ECS via Docker Compose


I'm currently using Docker Compose AWS ECS to deploy a backend server. Currently the backend server is listening to port 80. I have a domain I own that has a dedicated hosted zone on Route 53.

I'm wanting to create a subdomain (aka server.dev.example.com) that will point the backend server but am having extraordinary difficulty figuring out how to successfully associate the subdomain to the backend server. The subdomain server.dev.example.com is certified via Amazon's Certificate Manager and I have the appropriate CNAME record created in my AWS Hosted zone. Currently I have the following docker-compose.yml file:

version: "3.9"
services:
  backend-server:
    image: PATH_TO_ECR_URL
    ports:
      - "80:80" 
 
x-aws-cloudformation:
  Resources:
    BackendserverTCP80Listener:
      Properties:
        Certificates:
          - CertificateArn: "ARN_TO_CERTIFIED_SUBDOMAIN"
        Protocol: HTTPS
        Port: 443

I am able to successfully deploy to AWS however when I curl the subdomain (aka server.dev.example.com) I receive curl: (6) Could not resolve host. The browser reports DNS address could not be found. Diagnosing the problem. DNS_PROBE_POSSIBLE.

How do I correctly set up the docker-compose.yml file such that I can associate the subdomain with the backend server that's deployed? I'm hoping that with whatever solution I end up with will not require additional configuration outside of the standard deployment of calling docker-compose up.

The CloudFormation template file generated from the docker-compose file is below:

AWSTemplateFormatVersion: 2010-09-09
Resources:
  CloudMap:
    Properties:
      Description: Service Map for Docker Compose project server
      Name: server.local
      Vpc: vpc-xxx
    Type: AWS::ServiceDiscovery::PrivateDnsNamespace
  Cluster:
    Properties:
      ClusterName: backend-server
      Tags:
        - Key: com.docker.compose.project
          Value: backend-server
    Type: AWS::ECS::Cluster
  Default80Ingress:
    Properties:
      CidrIp: 0.0.0.0/0
      Description: backend-server:80/tcp on default network
      FromPort: 80
      GroupId:
        Ref: DefaultNetwork
      IpProtocol: TCP
      ToPort: 80
    Type: AWS::EC2::SecurityGroupIngress
  DefaultNetwork:
    Properties:
      GroupDescription: backend-server Security Group for default network
      Tags:
        - Key: com.docker.compose.project
          Value: backend-server
        - Key: com.docker.compose.network
          Value: backend-server_default
      VpcId: vpc-xxx
    Type: AWS::EC2::SecurityGroup
  DefaultNetworkIngress:
    Properties:
      Description: Allow communication within network default
      GroupId:
        Ref: DefaultNetwork
      IpProtocol: "-1"
      SourceSecurityGroupId:
        Ref: DefaultNetwork
    Type: AWS::EC2::SecurityGroupIngress
  LoadBalancer:
    Properties:
      Scheme: internet-facing
      SecurityGroups:
        - Ref: DefaultNetwork
      Subnets:
        - subnet-xxx
        - subnet-xxx
        - subnet-xxx
        - subnet-xxx
        - subnet-xxx
        - subnet-xxx
      Tags:
        - Key: com.docker.compose.project
          Value: backend-server
      Type: application
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
  LogGroup:
    Properties:
      LogGroupName: /docker-compose/backend-server
    Type: AWS::Logs::LogGroup
  BackendserverService:
    DependsOn:
      - BackendserverTCP80Listener
    Properties:
      Cluster:
        Fn::GetAtt:
          - Cluster
          - Arn
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DeploymentController:
        Type: ECS
      DesiredCount: 1
      LaunchType: FARGATE
      LoadBalancers:
        - ContainerName: backend-server
          ContainerPort: 80
          TargetGroupArn:
            Ref: BackendserverTCP80TargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - Ref: DefaultNetwork
          Subnets:
            - subnet-xxx
            - subnet-xxx
            - subnet-xxx
            - subnet-xxx
            - subnet-xxx
            - subnet-xxx
      PlatformVersion: 1.4.0
      PropagateTags: SERVICE
      SchedulingStrategy: REPLICA
      ServiceRegistries:
        - RegistryArn:
            Fn::GetAtt:
              - BackendserverServiceDiscoveryEntry
              - Arn
      Tags:
        - Key: com.docker.compose.project
          Value: backend-server
        - Key: com.docker.compose.service
          Value: backend-server
      TaskDefinition:
        Ref: BackendserverTaskDefinition
    Type: AWS::ECS::Service
  BackendserverServiceDiscoveryEntry:
    Properties:
      Description: '"backend-server" service discovery entry in Cloud Map'
      DnsConfig:
        DnsRecords:
          - TTL: 60
            Type: A
        RoutingPolicy: MULTIVALUE
      HealthCheckCustomConfig:
        FailureThreshold: 1
      Name: backend-server
      NamespaceId:
        Ref: CloudMap
    Type: AWS::ServiceDiscovery::Service
  BackendserverTCP80Listener:
    Properties:
      DefaultActions:
        - ForwardConfig:
            TargetGroups:
              - TargetGroupArn:
                  Ref: BackendserverTCP80TargetGroup
          Type: forward
      LoadBalancerArn:
        Ref: LoadBalancer
      Port: 443
      Protocol: HTTPS
      Certificates:
        - CertificateArn: CERT_ARN
    Type: AWS::ElasticLoadBalancingV2::Listener
  BackendserverTCP80TargetGroup:
    Properties:
      Port: 80
      Protocol: HTTP
      Tags:
        - Key: com.docker.compose.project
          Value: backend-server
      TargetType: ip
      VpcId: vpc-xxx
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
  BackendserverTaskDefinition:
    Properties:
      ContainerDefinitions:
        - Command:
            - us-east-1.compute.internal
            - backend-server.local
          Essential: false
          Image: docker/ecs-searchdomain-sidecar:1.0
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group:
                Ref: LogGroup
              awslogs-region:
                Ref: AWS::Region
              awslogs-stream-prefix: backend-server
          Name: Backendserver_ResolvConf_InitContainer
        - DependsOn:
            - Condition: SUCCESS
              ContainerName: Backendserver_ResolvConf_InitContainer
          Essential: true
          Image: IMAGE_URL
          LinuxParameters: {}
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group:
                Ref: LogGroup
              awslogs-region:
                Ref: AWS::Region
              awslogs-stream-prefix: backend-server
          Name: backend-server
          PortMappings:
            - ContainerPort: 80
              HostPort: 80
              Protocol: tcp
      Cpu: "256"
      ExecutionRoleArn:
        Ref: BackendserverTaskExecutionRole
      Family: backend-server
      Memory: "512"
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
    Type: AWS::ECS::TaskDefinition
  BackendserverTaskExecutionRole:
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Condition: {}
            Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
      Tags:
        - Key: com.docker.compose.project
          Value: backend-server
        - Key: com.docker.compose.service
          Value: backend-server
    Type: AWS::IAM::Role

Solution

  • I discovered I was missing an ingress rule in my load balancer that accepted port 443. This was done by adding the following option in my docker-compose file:

    x-aws-cloudformation:
      Resources:
        BackendserverTCP80Listener:
          Properties:
            Certificates:
              - CertificateArn: "ARN_CERT"
            Protocol: HTTPS
            Port: 443
    
        # This is what I added below
        Default443Ingress:
          Properties:
            CidrIp: 0.0.0.0/0
            Description: backend-server:443/https on default network
            FromPort: 443
            GroupId:
              Ref: DefaultNetwork
            IpProtocol: TCP
            ToPort: 443
          Type: AWS::EC2::SecurityGroupIngress