Deploy a NGINX docker container on ECS Fargate using Cloudformation
Posted on: 28-2-2024
To deploy a NGINX docker container on ECS Fargate using Cloudformation we need to follow the following steps:
- Create a Dockerfile
- Push Docker image to ECR
- Create a template file
- Create a Cluster Resource
- Create a TaskDefinition
- Create a Cluster Service
- Deploy
This tutorial assumes you have a working nginx config file and some files to serve. For example the output of a ng build command.
Create a Dockerfile.
Create a Dockerfile in the root of your project.
# Use nginx as base image
FROM nginx:latest
# Copy the nginx config file to the container
COPY nginx.conf /etc/nginx/nginx.conf
# Copy files you want to serve to the container
COPY /dist/<your-app> /www/data
# Metadata that tells the user the container listens on port 80
EXPOSE 80
Push Docker image to ECR
We need to push our docker image to ECR because we want to use this image in our Cloudformation template.ECR stands for Elastic Container Registry and is a place on AWS to store docker images.
Create a repository in AWS using for example the console. Follow the push commands to push your image to your created ECR repository.
Create a template file
We want to use Cloudformation to use Infrastructure as Code to deploy services. For this, we need to add a yml file which is our configuration file containing all services we want to use.Create a file named cloudformation-template.yml in the root of your project.
Create a Cluster Resource
To deploy a docker container on ECS Fargate it is useful to understand how ECS is build up. You have got a cluster which is made up of services, which contains tasks which are created based on task-definitions.Add the following resource to your template:
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: 'cf-nginx-angular-cluster'
CapacityProviders:
- 'FARGATE'
This creates a ECS Fargate cluster with the name 'cf-nginx-angular-cluster'
Create a TaskDefinition
Add the following resource to your template: TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
ContainerDefinitions:
- Name: 'cf-nginx-angular-task-definition'
Image: '<your-image-uri>'
PortMappings:
- AppProtocol: http
ContainerPort: 80
HostPort: 80
Protocol: tcp
Cpu: 256
Memory: 512
ExecutionRoleArn: !GetAtt ExecutionRole.Arn
Family: 'cf-nginx-angular-task-definition-family'
NetworkMode: awsvpc
RequiresCompatibilities:
- 'FARGATE'
Our task-definition needs an execution-role so it can make AWS API calls on our behalf.
That's why we also need to add the following resource to our template:
ExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: 'cf-nginx-angular-ecs-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
Effect: 'Allow'
Principal:
Service:
- ecs-tasks.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
This creates an IAM Role with the managed policy 'AmazonECSTaskExecutionRolePolicy' which is a policy that provides access to other AWS service resources that are required to run Amazon ECS tasks.
Create a Cluster Service
Add the following the resource to your template: ECSService:
Type: AWS::ECS::Service
Properties:
ServiceName: 'cf-nginx-angular-service'
Cluster: !GetAtt ECSCluster.Arn
DesiredCount: 1
LaunchType: 'FARGATE'
TaskDefinition: !Ref TaskDefinition
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: 'ENABLED'
SecurityGroups:
- !GetAtt SecurityGroup.GroupId
Subnets:
- '<your-subnet>'
Because we want our deployed nginx container to be accessible from the internet we need to create a security group wich allows such access and reference this security group from our service resource.
That's why we also need to add the following resource:
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: 'cf-nginx-angular-security-group'
GroupDescription: 'Allows http requests from everywhere'
SecurityGroupIngress:
CidrIp: '0.0.0.0/0'
IpProtocol: tcp
FromPort: 80
ToPort: 80
This security group contains an inbound rule that states that all http trafic on port 80 is allowed.
Deploy
This is how your cloudformation-template.yml should look like:AWSTemplateFormatVersion: 2010-09-09
Resources:
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: 'cf-nginx-angular-cluster'
CapacityProviders:
- 'FARGATE'
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
ContainerDefinitions:
- Name: 'cf-nginx-angular-task-definition'
Image: '357839314461.dkr.ecr.eu-central-1.amazonaws.com/test:latest'
PortMappings:
- AppProtocol: http
ContainerPort: 80
HostPort: 80
Protocol: tcp
Cpu: 256
Memory: 512
ExecutionRoleArn: !GetAtt ExecutionRole.Arn
Family: 'cf-nginx-angular-task-definition-family'
NetworkMode: awsvpc
RequiresCompatibilities:
- 'FARGATE'
ECSService:
Type: AWS::ECS::Service
Properties:
ServiceName: 'cf-nginx-angular-service'
Cluster: !GetAtt ECSCluster.Arn
DesiredCount: 1
LaunchType: 'FARGATE'
TaskDefinition: !Ref TaskDefinition
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: 'ENABLED'
SecurityGroups:
- !GetAtt SecurityGroup.GroupId
Subnets:
- '<your-subnet>'
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: 'cf-nginx-angular-security-group'
GroupDescription: 'Allows http requests from everywhere'
SecurityGroupIngress:
CidrIp: '0.0.0.0/0'
IpProtocol: tcp
FromPort: 80
ToPort: 80
ExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: 'cf-nginx-angular-ecs-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
Effect: 'Allow'
Principal:
Service:
- ecs-tasks.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'
Validate the file using the following command:
aws cloudformation validate-template --template-body file://cloudformation-template.yml
Run the follow command to deploy your nginx container using cloudformation.
aws cloudformation create-stack --stack-name cf-nginx-angular-stack --template-body file://./cloudformation-template.yml --capabilities CAPABILITY_NAMED_IAM
Because we create a resource of type AWS::IAM::Role with a custom name we need to use the flag --capabilities CAPABILITY_NAMED_IAM.
Navigate to your deployed task and go to its public ip.
Now view your deployed app!