Containers

Build preview environments for Amazon ECS applications with AWS Copilot

Introduction

In the software development sphere, immediate evaluation of every code adjustment and deploying pull requests to active environments for immediate preview and feedback is essential. This practice is instrumental in reducing post-deployment issues and operational disruptions, underscoring the urgency for dedicated preview environments. Without these environments, the risk of merging unassessed features into the primary codebase rises, potentially leading to substantial operational and project management challenges. To counteract this, this post explores the development of preview environments for Amazon Elastic Container Service (Amazon ECS) employing AWS Copilot, with a seamless integration with GitLab. In this structure, each change in the branch autonomously sparks the formation of a distinct environment in AWS ECS using AWS Copilot. This automation not only enhances preview accuracy but also accelerates the feedback loop, boosting the overall efficiency and reliability of the development process.

Using AWS Copilot eliminates the need for manual setups, ensuring a consistent and error-free environment deployment. Moreover, integrating with existing GitLab setups allows teams to augment their existing infrastructure, ensuring a seamless, effective shift to this sophisticated, automated development model. Constructing preview environments in Amazon ECS using AWS Copilot, combined with GitLab, emerges as a strategic approach for enhancing software development projects’ efficiency and quality. This approach ascertains that every code modification is meticulously examined, tested, and previewed in a dedicated setting, establishing a foundation for solid, reliable software delivery.

Thus, in this post’s walkthrough, we outline the steps to build preview environments using AWS Copilot in GitLab.

Solution overview

The target architecture is designed for the efficient creation of reusable preview environments using Git feature branches. Developers branching out for feature work trigger the setup of preview environments on Amazon ECS, facilitated by AWS Copilot. This process is integrated into the GitLab continuous integration/continuous delivery (CI/CD) pipeline, where Copilot commands are executed to establish new environments and deploy microservices into Amazon ECS without any disruption. At the point of merging, these environments are systematically decommissioned using the same AWS Copilot commands, ensuring resource optimization. This method streamlines the development workflow, ensuring environments are managed effectively throughout their lifecycle.

Architecture overview: A git repository in Gitlab is used to deploy preview environments into multiple AWS environments using AWS Copilot

Prerequisites

To reproduce the setup described in this post you need the following prerequisites:

  1. Git and GitLab experience: This post is targeted towards existing users of GitLab. Thus, for a streamlined experience, it’s recommended that readers bring a foundational understanding of Git and GitLab. While we aim to be as comprehensive as possible, familiarity with these tools will aid in understanding the nuances of the solution. You can follow this link for Git documentation, and this link for GitLab
  2. GitLab Runner deployment: The GitLab Runner, central to our setup, is assumed to be deployed in an AWS account designated as Tooling-/ CI/CD-Account. This can be either an Amazon Elastic Compute Cloud (Amazon EC2) instance, or a container deployed in Amazon ECS. Crucially, this runner should have the necessary permissions to assume roles in both the Development and Production AWS Accounts, allowing it to deploy the AWS Copilot environments. You can check this link to set up your own custom runner in an AWS.
  3. You need to install following tools into your local environment:
    1. aws-cli v2
    2. docker
  4. aws-copilot-cli: The AWS Copilot command line tool.
  5. AWS Account: You need an AWS account and IAM permissions to perform the bootstrap script.

Note that this post was tested on aws cli version: 2.13.5 and aws-copilot version: v1.32.0, and docker version: 24.0.6.

Walkthrough

In order to build preview environments using Gitlab CI pipelines and AWS Copilot in AWS, you need to follow these steps:

  • Creating a GitLab repository.
  • Building and pushing CI image to the GitLab Registry
  • Building AWS Copilot application (App) and development (Dev) environments using bootstrap scripts
  • Setting up GitLab Pipeline environment variables
  • Pushing code to the GitLab repository.

Bootstrapping infrastructure

Before we begin, you can check this repository to have a complete source code. You will prepare the CI image, and bootstrap the environment in this step.

Preparing CI Image

To run the docker based tasks in the GitLab pipeline, we must specify a Docker image in the Gitlab-ci configuration file. This image is needed to carry out each part of the process in separate Docker containers. Thus, we need to build a docker image and push it to the registry to execute all the stages in our build pipeline. We will use the GitLab’s own image registry to store the CI image.

Here is the dockerfile that we are going to use as CI image:

FROM alpine:3.18.5
RUN apk --no-cache add curl aws-cli git jq docker

RUN curl -Lo copilot "https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux" && \
    chmod +x copilot && \
    mv copilot /usr/local/bin/copilot && \
    copilot --help

We use the newest version of the alpine base image that is available at time of writing of this post. Please make sure to check for updates and possible vulnerabilities that might be discovered at a later point in time.

You need to login to GitLab Registry, then build the image and push it. You can get the details by opening the DeployContainer Registry option from the left side of the GitLab menu, then choose the CLI commands button to view the below commands:

docker login registry.gitlab.com

docker build --platform linux/x86_64 -t registry.gitlab.com/<YOUR-GITLAB-USER-NAME>/aws-ecs-gitlab-copilot .
docker push registry.gitlab.com/<YOUR-GITLAB-USER-NAME>/aws-ecs-gitlab-copilot

A screenshot of the Gitlab UI showing the container registry

Note: When you clone the sample repository, please make sure to change the image property with your GitLab Registry url.

image: registry.gitlab.com/<YOUR-GITLAB-USER-NAME>/aws-copilot-with-gitlab

Building App and Dev environments

To bootstrap initial AWS Copilot App and Dev environments, you can clone this repository to use the bootstrap.sh under the scripts folder.

But before that, you need to setup your AWS configuration using environment variables. These variables are going to be needed in the execution of the bootstrap script. First, you need to configure your AWS profile for the Dev account. You can check this documentation for setting up the AWS profile for Dev account.

export APP_NAME="copilot-demo-app"      # This defines the Copilot app's name
export DEV_ENV_NAME="dev"               # This defines the development environment name
export DEV_PROFILE="dev"                # This is the AWS profile name will be used by copilot env. 
export AWS_PROFILE="dev"                # This is needed by copilot app init command.

Then, you can run the bootstrap script to initialize whole environment.

chmod +x ./scripts/bootstrap.sh && ./scripts/bootstrap.sh

A screenshot of the output of the bootstrap.sh script.

The script creates a Virtual Private Cloud (VPC), with two private, two public subnets, and all configured routings. It then creates an Amazon ECS cluster inside the VPC, and builds and deploys the two microservices (i.e., service-a, and service-b) into the Amazon ECS cluster. At the end of the script output, take note of the VPC_ID, PUBLIC_SUBNETS, and PRIVATE_SUBNETS, which we will need in the next step.

This is the generated architecture by running the bootstrap script.

A screenshot of the AWS Console showing an ECS cluster.

These are the deployed services into the Dev cluster.

A screenshot of the AWS ECS console showing services A and B being deployed.

After completing the bootstrap script, you’ll notice that there are generated manifests files for AWS Copilot in your repository. You can update the manifest files to make changes to your environment later, and commit to your existing branches. The copilot folder has the following objects:

  • environments: This folder includes the manifest files of your environments. You can check the details in the environments section of AWS Copilot documentation.
  • service-a and service-b: Since we deploy two services called “service-a” and “service-b” you can see service-a and service-b folders corresponding to the created ecs services using the copilot svc
  • .workspace: This file includes the current AWS Copilot application name.

A screenshot of the copilot folder containing manifests and other configuration files.

Set up Gitlab Pipeline

Architecture diagramm: A vpc contaiing two public and two private subnets. Services A and B are deployed in the private subnets.

This is the created Dev cluster within the Amazon ECS Console:

Once you push the repo to GitLab, you need to set up the environment variables which are required to run the pipeline. To do that, we need to put the following environment variables in the Settings CI/CDVariables menu in GitLab. Please make sure that you uncheck Protect variable in the flags section Add Variable or the variables can be read only in protected branches.

  • APP_NAME : Put your AWS Copilot App name you defined in the bootstrap section
  • AWS_REGION : Put your AWS Region (i.e., eu-west-1)
  • VPC_ID: You can get this one from bootstrap script result, or directly go to the VPC console, and find the VPC and get the id.
  • PUBLIC_SUBNETS: You can get this one from bootstrap script result, or directly go to the VPC console, and find the Public subnets and get the ids in comma-separated string.
  • PRIVATE_SUBNETS: You can get this one from bootstrap script result, or directly go to the VPC console, and find the Public subnets and get the ids in comma-separated string

A screenshot of the Gitlab UI for adding environment variables.

Complete view of the environment variables in the GitLab console.

A screenshot of GItlab UI for managing environment variables.

Now, we need to push our repository to GitLab and check the pipeline results. Since we are working on main branch currently, the pipeline will deploy and update the environment and services into the Dev environment since we bootstrapped in the bootstrap script.

The pipeline will skip the init and deploy stages for Dev environment as they are already initialized by the bootstrap script. the pipeline will go to the svc-deploy stage to deploy service-a and service-b. There is also optional env-stop, which can be triggered manually to clean up the Dev environment if it is not used anymore.

A screenshot of Gitlab UI showing the pipeline execution.

Preview environments

Now let’s move to building of the preview environments using merge request pipelines. Once the developer creates a new feature branch to update the service, they can push to the GitLab repository to create a preview environment from the feature branch.

git checkout -b feature/my-new-feature1

# Let's update the service-a/app/app.py and service-b/app/app.py

git commit -am "My new feature for service-a and service-b"
git push --set-upstream origin feature/my-new-feature1

This will first initialize the preview environment using the VPC_ID and PUBLIC/PRIVATE subnets created in bootstrap script. Then, it deploys the services into the preview environment.

A screenshot of Gitlab UI showing the pipeline execution.

Created new preview environment by feature branch in the Amazon ECS Console, and services are being deployed into this environment.

Screenshot of AWS ECS console showing a new ECS cluster that has been deployed for the preview environment.

Merge request and cleaning preview environments

When the developers finish their testing, they can create a merge request to the main branch to be deployed into the dev environment. In this case, the merge request will create new pipeline which only terminates the preview environment.

Let’s create a new merge request in GitLab using the CodeMerge Requests menu from our feature branch to the main branch. It will show up a new pipeline called preview-env-stop, which you can select to terminate whole preview environment after approving the merge request.

A screenshot of GItlab UI showing a new button that can be used to destroy a preview environment after it has been merged.

Cleaning of preview environment.

A screenshot of the output for the environment deletion.

Cleaning up

For the cleanup of environments deployed in this post, you should first execute the env-stop stages of your GitLab jobs to delete any branch-specific environments. Then navigate to the AWS CloudFormation console and delete the CloudFormation stacks associated with your application. Furthermore, you need to delete the Elastic Container Repository repositories used to store your container images.

Conclusion

In this post, we showed you the strategic implementation of AWS Copilot within the GitLab CI pipeline marks a significant advancement in software development practices. By enabling the automatic creation and disposal of preview environments for every feature branch in Amazon ECS, developers are empowered to thoroughly review and test code changes in real-time. This framework not only mitigates the risks associated with unvetted features entering the production codebase but also greatly enhances operational efficiency and project management. The seamless integration of AWS Copilot and GitLab fosters a more agile, responsive, and consistent development workflow, ultimately elevating the quality and reliability of software delivery. As this post has demonstrated, adopting this architecture is more than a technical upgrade—it is a fundamental shift towards a more resilient and proactive development ecosystem. How do you achieve rapid feedback cycles during development? Let us know via our social media channels.

Wladi Mitzel

Wladi Mitzel

Wladi Mitzel is a Solutions Architect at AWS, located in Frankfurt am Main, Germany. He supports customers in cloud migrations and helps build resilient, secure and cost efficient cloud architectures. Besides container technologies Wladi is interested in environmental sustainability and how customers can leverage cloud to lower their environmental footprint. Connect with him on LinkedIn at /in/wladislaw-mitzel

Ahmet Atalay

Ahmet Atalay

Ahmet Atalay is a DevOps Architect at AWS, located in Amsterdam, Netherlands. He helps customers to use best practices and reference architectures while they are managing their cloud infrastructure, and designing and delivering applications. He loves Containers, Serverless, and DevOps technologies. You can find him on Twitter at @ahmetfrkatalay, and on Linkedin at /in/ahmetatalay