DEV Community

Cover image for Automate Docker container deployment to AWS ECS using CloudFormation
Dmitriy A. for appfleet

Posted on • Originally published at

Automate Docker container deployment to AWS ECS using CloudFormation

Deploying Docker containers to AWS Elastic Container Service (ECS) is straightforward and automated when you make use of CloudFormation to define your infrastructure in a YAML template. Here we'll be running through a simple example where we'll setup everything required to run an NGINX container in AWS and access it over the internet.

AWS ECS overview

We've chosen to run the NGINX official Docker image as it will allow us to browse to port 80 and view the response to prove the container is running. To get this deployed into ECS, we'll need the following buildings blocks:

  • ECS Task Definition: a specification of your container, including what Docker image to use, what ports to expose, and what hardware resources to allocate
  • ECS Task: a running instance of the ECS Task Definition. Equivalent to a running Docker container.
  • ECS Service: responsible for running instances of your task definition, including how many to deploy, networking, and security
  • ECS Cluster: a grouping of ECS services and tasks
  • ECS Task Execution role: an IAM role which the task will assume, in our case allowing log events to be written to CloudWatch
  • Security Group: a security group can be attached to an ECS Service. We will use it to define rules to allow access into the container on port 80.

Alt Text

AWS ECS Launch Types

ECS tasks can be run in 2 modes, depending on your requirements:

  1. EC2: you are responsible for provisioning the EC2 instances on which your tasks will run.
  2. Fargate: AWS will provision the hardware on which your tasks will run. All you need to do is specify the memory and CPU requirements. Note that Fargate currently only supports nonpersistent storage volumes.

We'll be using the Fargate launch type in this example as it's the quickest way to get started. โœ”๏ธ


To keep this example as simple as possible, we're going to assume you already have the following setup:

  • an AWS account with AWS CLI access setup
  • a default VPC (AWS creates this by default when you create an AWS account)

Building the ECS using CloudFormation

We're going to use the YAML flavour of CloudFormation, and build up a stack piece by piece until we have an NGINX container running which we can access over the internet.

I recommend IntelliJ IDEA for editing CloudFormation templates, as it has a plugin which will provide syntax validation.

Creating the ECS cluster, log group, execution role, and security group

Start off by creating a file ecs.yml, and adding the following definitions:

AWSTemplateFormatVersion: "2010-09-09"
    Type: String
    Type: AWS::ECS::Cluster
      ClusterName: deployment-example-cluster
    Type: AWS::Logs::LogGroup
      LogGroupName: deployment-example-log-group
    Type: AWS::IAM::Role
      RoleName: deployment-example-role
          - Effect: Allow
            Action: sts:AssumeRole
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
    Type: AWS::EC2::SecurityGroup
      GroupName: ContainerSecurityGroup
      GroupDescription: Security group for NGINX container
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80

Our template takes only one parameter, SubnetID, to specify which subnet to deploy the ECS Task into. On a normal production setup, you'll want to deploy to multiple subnets across availability zones for high availability.

The AWS::ECS::Cluster resource requires no configuration other than a name.

Log group
The ECS task will log the application logs to this log group.

Execution role
This is the role that will be assumed by the ECS Task during execution. As such, it needs the provided assume role policy document, which allows ECS Tasks to assume this role.

It also has attached the AmazonECSTaskExecutionRolePolicy which contains the logs:CreateLogStream and logs:PutLogEvents actions, amongst others.

Security group
The security group defines what network traffic will be allowed access to the ECS Task. In our case, we just need to access port 80, the default NGINX port.

Let's apply this template with the following AWS CLI command, which creates a CloudFormation stack provisioning the above resources. Remember to replace <subnet-id> with your own subnet.

$ aws cloudformation create-stack --stack-name example-deployment --template-body file://./ecs.yml --capabilities CAPABILITY_NAMED_IAM --parameters 'ParameterKey=SubnetID,ParameterValue=<subnet-id>'

Eventually you'll see that the following resources have been created if you navigate in the AWS Console to CloudFormation > Stacks> example-deployment > Resources:

Alt Text

Creating the task definition and service

Add the following definition to the end of your ecs.yml CloudFormation template:

//previous template code  
    Type: AWS::ECS::TaskDefinition
      Family: deployment-example-task
      Cpu: 256
      Memory: 512
      NetworkMode: awsvpc
      ExecutionRoleArn: !Ref ExecutionRole
        - Name: deployment-example-container
          Image: nginx:1.17.7
            - ContainerPort: 80
            LogDriver: awslogs
              awslogs-region: !Ref AWS::Region
              awslogs-group: !Ref LogGroup
              awslogs-stream-prefix: ecs
        - EC2
        - FARGATE
    Type: AWS::ECS::Service
      ServiceName: deployment-example-service
      Cluster: !Ref Cluster
      TaskDefinition: !Ref TaskDefinition
      DesiredCount: 1
      LaunchType: FARGATE
          AssignPublicIp: ENABLED
            - !Ref SubnetID
            - !GetAtt ContainerSecurityGroup.GroupId

Task definition
We're defining an AWS::ECS::TaskDefinition with the following important properties:

  • the family is a way to group different versions of the same task definition
  • we're specifying how much hardware resources to dedicate to this task
  • we're using network mode awsvpc which is required for the Fargate launch type. This network mode means that our task will have the same networking capabilities as an EC2 instance, such as it's own IP address.
  • the execution role which we defined earlier
  • a container definition, specifying the image, the container port, and the logging configuration to tell it to log using the awslogs log driver (i.e to CloudWatch)
  • we specify that this task definition is compatible with both the EC2 and Fargate launch types (although we'll be using Fargate)

We're defining an AWS::ECS::Service with the following properties:

  • the ECS cluster into which this service will deploy tasks
  • the task definition to be deployed
  • the number of instances to run. For this simple example, we'll run 1, but for high availability, you'll want to run at least 2.
  • the launch type of Fargate so we don't have to worry about provisioning hardware
  • a network configuration which specifies the fact that we want a public IP address, the subnet to use for the service, and the security group to apply

Let's update the CloudFormation stack now with an update-stack command:

$ aws cloudformation update-stack --stack-name example-deployment --template-body file://./ecs.yml --capabilities CAPABILITY_NAMED_IAM --parameters 'ParameterKey=SubnetID,ParameterValue=<subnet-id>'

Wait a few moments, then you can see that some more resources have been created in our CloudFormation stack:

Alt Text

Getting a handle on our ECS resources

Head on over to ECS > Services and we'll check out what's been created. ๐Ÿ”

Alt Text

You'll see the deployment-example-cluster which importantly has 1 service and 1 running task:

Click on the cluster, then click on the Tasks tab:

Alt Text

Here you can see we're using the task definition we defined in the CloudFormation, the task status is running, and the launch type is Fargate.

Click on the task id for more details. Here's the Network section of the details page:

Alt Text

You can see here we've been provided with the public IP address of the task. Go ahead and try hitting that IP in your browser:

Alt Text

Looks like we got ourselves an NGINX!

Final words

To cleanup, just run the delete-stack command:

$ aws cloudformation delete-stack --stack-name example-deployment

Hopefully you've seen that it's straightforward to run Docker containers in ECS, and that AWS provides plenty of configuration options to have things working exactly as you like.

With CloudFormation, making incremental changes is straightforward, and it's a good option for managing an ECS Cluster.

Top comments (1)

wirelessben profile image

Nice tutorial, Dmitriy. This helped me.

"With CloudFormation, making incremental changes is straightforward, and it's a good option for managing an ECS Cluster. "

How would I make this "incremental change", like actually configuring the website? Wouldn't the container have to change? Or at least point to a static site populated from S3?

The article is long enough, so just a link would suffice.