Migrate from AWS Beanstalk to AWS ECS in a Simple Way

Igor Mardari
7code
Published in
5 min readOct 25, 2023

What will be our main goals?

  1. Setup the initial AWS infrastructure.
  2. Having a CI/CD pipeline which will deploy new changes.
  3. Having a public URL (HTTP for now) where we can access our application.

What we’ll deploy:

I’ll present a bash script that I’ve build for myself which will deploy a fully functional ECS application yet the most simple/basic one. It will consist of

  • VPC including public subnets and a Security Group
  • ECS Cluster
  • ECS Service
  • ECS Task
  • CloudWatch Log Group
  • ECR Repository
  • Load Balancer (including TargetGroup and Listener)

We’ll make intense use of AWS CLI.

Summary of the steps

  1. Create the Dockerfile.
  2. Create VPC, Public Subnets, Security Group.
  3. Copy and update ecs-task-definition.json .
  4. Copy and run: ./create-ecs-cluster.sh .

Notes

  • Make sure that the port you expose in the Dockerfile is the same as the one used for the load balancer
  • Make sure you update all the snippets.

Prepare the ecs-task-definition.json file:

This file describes how your container should behave, such as which Docker image to use, how many container instances to run, and what resources to allocate, among other configurations.

Here is an example to start with:

{
"family": "forum-api-dev",
"memory": "4096",
"cpu": "2048",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"executionRoleArn": "arn:aws:iam::645741524529:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "forum-api-container",
"image": "645741524529.dkr.ecr.us-east-1.amazonaws.com/forum-api-dev:latest",
"memory": 4096,
"cpu": 2048,
"essential": true,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/forum-api-dev",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"environment": [
]
}
]
}

Once these pre-requisites are set up, you’ll be in a great position to execute the migration script smoothly.

Step-by-step Migration:

Below is a bash script that guides you through the migration process.

TODO: modify it

#!/bin/bash

# Constants
VPC_ID="vpc-xxxxxx"
SUBNET1="subnet-xxxxxx"
SUBNET2="subnet-xxxxxx"
SECURITY_GROUP="sg-xxxxxx"
TASK_DEFINITION_FILE="ecs-dev-task-definition.json"
APP_NAME="forum-api-dev"
CLUSTER_NAME="${APP_NAME}-cluster"
CONTAINER_NAME="forum-api-container"


# Create ECS Cluster
aws ecs create-cluster --cluster-name $CLUSTER_NAME

# Create Target Group
TARGET_GROUP_ARN=$(aws elbv2 create-target-group --name ${APP_NAME}-target-group --protocol HTTP --port 80 --vpc-id $VPC_ID --target-type ip | jq -r '.TargetGroups[0].TargetGroupArn')

# Create Load Balancer
LOAD_BALANCER_ARN=$(aws elbv2 create-load-balancer --name ${APP_NAME}-alb --subnets $SUBNET1 $SUBNET2 --security-groups $SECURITY_GROUP --scheme internet-facing --type application | jq -r '.LoadBalancers[0].LoadBalancerArn')

# Create Listener
aws elbv2 create-listener --load-balancer-arn $LOAD_BALANCER_ARN --protocol HTTP --port 80 --default-actions Type=forward,TargetGroupArn=$TARGET_GROUP_ARN

# Register Task Definition
REGISTERED_TASK_DEF=$(aws ecs register-task-definition --cli-input-json file://$TASK_DEFINITION_FILE | jq -r '.taskDefinition.family + ":" + (.taskDefinition.revision | tostring)')

# Create ECS Service
aws ecs create-service \
--cluster $CLUSTER_NAME \
--service-name ${APP_NAME}-service \
--task-definition $REGISTERED_TASK_DEF \
--desired-count 2 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[$SUBNET1],securityGroups=[$SECURITY_GROUP],assignPublicIp=ENABLED}" \
--load-balancers "targetGroupArn=$TARGET_GROUP_ARN,containerName=${CONTAINER_NAME},containerPort=80" \
--deployment-configuration "maximumPercent=200,minimumHealthyPercent=100,deploymentCircuitBreaker={enable=true,rollback=true}"

echo "Setup completed successfully!"
  1. Setting Up Your VPC: Start by creating a public VPC, security group, and public subnets.
  2. Preparing the ECS Task Definition: The ecs-task-definition.json contains all the necessary configurations for your container, such as memory, CPU, network mode, and more. Here's an example:
{
"family": "forum-api-dev",
"memory": "4096",
"cpu": "2048",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"executionRoleArn": "arn:aws:iam::645741524529:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "forum-api-container",
"image": "645741524529.dkr.ecr.us-east-1.amazonaws.com/forum-api-dev:latest",
"memory": 4096,
"cpu": 2048,
"essential": true,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/forum-api-dev",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
},
"environment": [
{"name": "ENV", "value": "development"}
]
}
]
}

Tip: You can use the AWS CLI command aws ecs register-task-definition to generate this file.

  1. Creating an ECS Cluster: This is the heart of your ECS setup. It’s where your services and tasks will run.
  2. Setting Up the Load Balancer: The Application Load Balancer (ALB) distributes incoming application traffic across multiple targets, such as Amazon EC2 instances, containers, IP addresses, etc.
  3. Registering the Task Definition: This step involves pushing your ecs-task-definition.json to ECS.
  4. Creating the ECS Service: Here, you define how your task should run, such as the desired count of tasks, network configurations, and the load balancer to use.

Bonus: Integrating Deployment with GitHub Actions

Make your deployment seamless by integrating it with GitHub Actions:

name: DEV - Deploy to Amazon ECS
on:
push:
branches:
- develop
- feature/deploy-aws

env:
AWS_REGION: us-east-1
ECR_REPOSITORY: forum-api-dev # adjust this
ECS_SERVICE: forum-api-dev-service # adjust this
ECS_CLUSTER: forum-api-dev-cluster # adjust this
ECS_TASK_DEFINITION: ./ecs-dev-task-definition.json # adjust this
CONTAINER_NAME: forum-api-container # this value should be got from the above filePath

jobs:
deploy:
name: DEV - Deploy to Amazon ECS
runs-on: ubuntu-latest
environment: development

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@0e613a0980cbf65ed5b322eb7a1e075d28913a83
with:
aws-access-key-id: AKIAZMWJRWYYW5Y5VCAC
aws-secret-access-key: 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
aws-region: us-east-1

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@62f4f872db3836360b72999f4b87f1ff13310f3a

- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
# Build a docker container and
# push it to ECR so that it can
# be deployed to ECS.
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@c804dfbdd57f713b6c079302a4c01db7017a36fc
with:
task-definition: ${{ env.ECS_TASK_DEFINITION }}
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ steps.build-image.outputs.image }}

- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@df9643053eda01f169e64a0e60233aacca83799a
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true

This GitHub action automates your deployment process by building a Docker image, pushing it to Amazon Elastic Container Registry (ECR), and deploying it to ECS whenever there’s a push to specific branches.

Conclusion

Migrating from Beanstalk to ECS might seem daunting initially, but with a structured approach and the right tools, it can be a smooth transition. This guide aims to demystify the process and provide a step-by-step methodology for even those with little to no experience. Good luck with your migration!

--

--