AWS Copilot : The easiest way to host your APIs with high availability

hrvlnrv
7 min readJul 14, 2023

--

In this blog post I will show you how to host your web application with auto-scaling of containers behind a load balancer. By the end you will be able to create as many environments as you want such as staging, pre-production, production, …., store all of your sensitive secrets encrypted and inject them in your applications at startup. You will also be able to add any service in private subnet and automate deployment with Gitlab CI / CD or Github Action.

All this with basic knowledge in cloud engineering.

Cloud engineering has been coming the main topic when company wants to expose their web services to its customers. There are many tools to address these issues but if no one with appropriate skills can compare them, the wrong decision cannot be avoided.

Many containers orchestration tools need advanced knowledge to be used such as :

  • Kubernetes
  • Openshift
  • Hashicorp Nomad
  • Docker Swarm
  • Rancher

With nobody to maintain your infrastructure, these tools cannot be considered.

AWS Copilot is an open source command line interface that makes it easy for developers to build, release, and operate production ready containerized applications on AWS App Runner, Amazon ECS, and AWS Fargate. As their website says.

In this post we are going to deploy infrastructure through Amazon ECS service, if you are interested with other Copilot’s features here is the documentation

Prérequisites :

AWS CLI
Copilot CLI

You need to configure authentication to your AWS account : how to

Once it’s done, make sure your IAM user have AdministratorAccess policy permission. Copilot will creates many resources in your account such as Virtual Private Cloud, Elastic Load Balancer, ECR, ECS cluster and roles.

Deep dive into container orchestrator AWS managed

Every applications in your ECS cluster need to be containerized, it simplifies everything.

All my examples will be in NodeJS but it doesn’t matter, all you have to do is to adapt the Dockerfile for your needs. The next steps do not depend on the programming language.

FROM node:16.10-alpine

EXPOSE 80

WORKDIR '/app'

RUN npm install -g npm@8.18.0 --upgrade

ENV PORT=80

COPY . .

RUN npm install

ENTRYPOINT [ "node", "app.js" ]

Once is done you have to initialize your Copilot application

copilot app init <application-name>

And get this output

As you can see, Copilot creates one CloudFormation stack and IAM role to create AWS resources for next steps.

Now let’s create one environment such as staging

copilot env init --name staging

Choose your AWS account that you configured before and leave the default settings.

Then you have to deploy your newly created environment

copilot env deploy staging

Our environment is now ready to host applications.

Note that every environments will create a new ECS Cluster for more isolation and best control on each

As recommended two step before, we initialize Copilot application.

copilot init <service-name>

Request-Driven Web Service

An AWS App Runner service that autoscales your instances based on incoming traffic and scales down to a baseline instance when there’s no traffic.

Load Balanced Web Service

An ECS Service running tasks on Fargate with an Application Load Balancer, a Network Load Balancer or both, as ingress. This option is suitable for HTTP or TCP services with steady request volumes that need to access resources in a VPC or require advanced configuration.

Note that an Application Load Balancer is an environment-level resource, and is shared by all Load Balanced Web Services within the environment.

Backend Service

If you want a service that can’t be accessed externally, but only from other services within your application, you can create a Backend Service. Copilot will provision an ECS Service running on AWS Fargate, but won’t set up any internet-facing endpoints

Worker Service

Worker Services allow you to implement asynchronous service-to-service communication with pub/sub architectures.

Scheduled Job

As his name says scheduled jobs are useful to complete task based on cron expressions alongside web services in cluster.

In this article we will create Load Balanced Web Service and resolve it by domain name. Select it and choose a name for your application then set the path to your Dockerfile

You can edit the manifest created at ./copilot/<application-name>/manifest.yml according to your needs here is mine :

# The manifest for the "application" service.
# Read the full specification for the "Load Balanced Web Service" type at:
# https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/

name: application
type: Load Balanced Web Service

# Distribute traffic to your service.
http:
# Requests to this path will be forwarded to your service from load balancer.
# To match all requests you can use the "/" path.
path: '/'
healthcheck: '/healthcheck'

# Configuration for your containers and service.
image:
build:
dockerfile: src/Dockerfile
cache_from:
- 741609059380.dkr.ecr.eu-west-3.amazonaws.com/application/application
port: 80

# Default values
cpu: 256
memory: 512

# Configuration by environement
# Overwrites default values

environments:
staging:
cpu: 512
memory: 1024
# Range of scale
count:
range:
min: 1
max: 6
spot_from: 3
cpu_percentage: 70
memory_percentage: 80
variables:
ENVIRONMENT: STAGING

To create secrets and inject them at every deployment we will use AWS System Manager and more precisely Parameter Store. You can create secrets directly by the web console but don’t forget to add the correct tags. AWS Copilot use these tags to know which ones secret to use.

With AWS cli, ssm option gives you many choice to interact with their API

ENVIRONMENT_NAME=staging
APPLICATION_NAME=<application-name>
SECRET_NAME=STAGING_MONGO_URI.....
SECRET_VALUE=mongo://......

aws ssm put-parameter --name ${SECRET_NAME} --value ${SECRET_VALUE} --type SecureString --tags Key=copilot-environment,Value=${ENVIRONMENT_NAME} Key=copilot-application,Value=${APPLICATION_NAME}

And you can specify it by updating the manifest

The value of MONGO_URI will be recorvered by the name of the previsouly created secret.

environments:
staging:
cpu: 512
memory: 1024
count:
range:
min: 1
max: 6
spot_from: 3
cpu_percentage: 70
memory_percentage: 80
variables:
ENVIRONMENT: STAGING
secrets:
MONGO_URI: STAGING_MONGO_URI

It works the same way of environment variables except that values are encrypted in AWS System Manager.

Now, you can deploy the newly created manifest by doing this command

copilot svc deploy --env staging --tag 0.0.1

That’s it ! Your application is now responding on the host in command output.

Note : if you create Backend Service it can be resolved only by other services in the cluster

Domain Name configuration

If your domain is registered with AWS Route53, Copilot allows you to specify the domain in your Load Balanced Web Service manifest by doing this

http:
# Requests to this path will be forwarded to your service.
# To match all requests you can use the "/" path.
path: '/'
healthcheck: '/healthcheck'
alias:
- name: api.domain.com

Note that you can also attach SSL certificate in http section by adding associated ARN in AWS Certificate Manager more details here

If you use another registar than AWS Route53 you have to make this manually. Create record into domain name configuration which is a CNAME to the host where your Load Balanced Web Application is responding.

Automate deployment with CI / CD pipelines

In this section we gonna see how to automate application deployment with Copilot cli. It’s globally the same actions that we made before.

Gitlab CI / CD

To auto deploy your application, first of all you have to create .gitlab-ci.yml file at the root of your project and paste the following into it.

stages:
- deployment

variables:
COPILOT_SERVICE: <service-name>


copilot-staging:
stage: deployment
only:
- master
image: ubuntu:latest
script:
- apt update && apt install -y wget
- wget -O /usr/local/bin/copilot https://github.com/aws/copilot-cli/releases/download/v1.27.0/copilot-linux && chmod +x /usr/local/bin/copilot && copilot -v
- copilot svc deploy --env staging -n ${COPILOT_SERVICE} --tag "$(git describe --tags --always)-$(date +"%Y%m%d-%H%M%S" -u)"

Note that you have to set AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION / AWS_ACCESS_KEY_ID in your Gitlab repository secrets, without Copilot cannot authenticate itself to AWS

Github Action

Github CI / CD manifests have to be created in .github/workflows/

Let’s create file called deploy-ECS.yaml in this folder and paste the following manifest into it

name: Deploy ECS
run-name: ${{ github.actor }} trigger staging deployment 🚀

on:
push:
branch:
- main

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
- uses: ksivamuthu/aws-copilot-github-action@v0.0.1
with:
command: install
- run: |
copilot --version
- uses: ksivamuthu/aws-copilot-github-action@v0.0.1
with:
command: deploy
app: application
env: staging
force: false

Note that, as Gitlab you have to set secrets as AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION in your Github repository.

Congrats, you’ve created your first AWS hosted web application with Copilot !

--

--