Skip to content

CICD with AWS Fargate Service and GitHub Actions for Dockerized Strapi Applications

Posted on:May 3, 2023 at 07:22 AM

Introduction:

Strapi is a popular headless content management system (CMS) that allows developers to build customizable APIs with ease. In this blog post, we’ll walk you through the process of integrating a Dockerized Strapi application with AWS Fargate and GitHub Actions for a seamless CI/CD implementation.

By the end of this tutorial, you’ll have a Strapi application running on AWS Fargate, automatically deployed using GitHub Actions whenever you push changes to your GitHub repository.

Let’s get started!

Step 1: Create a Strapi Application for CI/CD Practice

Here we create a new Strapi application specifically for the purpose of demonstrating CI/CD practices. Follow the official Strapi documentation to set up your project.

Create a Strapi Project

Once your application is up and running, you can proceed to the next step. Remember that this Strapi application serves as a practice environment for CI/CD implementation and may require further customization and fine-tuning for real-world use cases.

Step 2: Dockerize the Strapi Application

To containerize your Strapi application, you need to create a Dockerfile. This file will contain instructions for building a Docker image of your application.

Dockerfile For development environment:

FROM node:16-alpine
# Installing libvips-dev for sharp Compatibility
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY ./package.json ./package-lock.json ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN npm install
WORKDIR /opt/app
COPY ./ .
RUN npm run build
EXPOSE 1337
CMD ["npm", "run", "develop"]

Don’t forget to include a .dockerignore file to exclude unnecessary files and folders from your Docker image.

.tmp/
.cache/
.git/
build/
node_modules/
.env
data/

To containerize your Strapi application for production, best practice is to create a Dockerfile.prod. This file will contain instructions for building a Docker image of your application optimized for production environments. You can follow the tutorial from Strapi’s official documentation to create a production-ready Dockerfile or use the sample file below:

FROM node:16-alpine as build
# Installing libvips-dev for sharp Compatibility
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev && rm -rf /var/cache/apk/* > /dev/null 2>&1
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY ./package.json ./package-lock.json ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN npm install --production
WORKDIR /opt/app
COPY ./ .
RUN npm run build

FROM node:16-alpine
# Installing libvips-dev for sharp Compatibility
RUN apk add vips-dev
RUN rm -rf /var/cache/apk/*
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/app
COPY --from=build /opt/node_modules ./node_modules
ENV PATH /opt/node_modules/.bin:$PATH
COPY --from=build /opt/app ./
EXPOSE 1337
CMD ["npm", "run","start"]

Step 3: Set Up AWS Fargate with GitHub Actions

Follow the tutorial provided by AWS to set up the GitHub Actions workflow for deploying your Strapi application to AWS Fargate:

Continuous delivery of container applications to AWS Fargate with GitHub Actions

This tutorial will guide you through creating an Amazon Elastic Container Registry (ECR) repository, configuring your GitHub Actions workflow file, and setting up the necessary AWS resources for your Fargate service.

Step 4: Configure Secret Manager

Create the following secret keys that have defined in the .env file:

APP_KEYS
API_TOKEN_SALT
ADMIN_JWT_SECRET
TRANSFER_TOKEN_SALT
DATABASE_CLIENT
DATABASE_FILENAME
JWT_SECRET

Follow the tutorial from AWS to configure AWS Secrets Manager for securely storing sensitive data such as database credentials, API keys, and other secrets:

AWS - Specifying Sensitive Data

This tutorial will help you create and manage secrets, as well as reference them in your task definition. Make sure to modify your task definition to include the necessary environment variables for your Strapi application, referencing the appropriate secrets from AWS Secrets Manager.

Modify your task-definition.json file as follow:

Remember to replace <your-container-name>, <your-aws-region>, <region>:<account-id>, and <your-secret-name> with the appropriate values

<your-container-imageURL> doesn’t matter here, as the GitHub Actions workflow will populate the image URL after building and pushing the docker image to ECR later on. For now, simply use the container name as a placeholder.

{
  "family": "sample-fargate",
  "containerDefinitions": [
    {
      "name": "<your-container-name>",
      "image": "<your-container-imageURL>",
      "portMappings": [
        {
          "containerPort": 1337,
          "protocol": "tcp"
        }
      ],
      "essential": true,
      "entryPoint": ["sh", "-c"],
      "environment": [],
      "mountPoints": [],
      "volumesFrom": [],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "firelens-container",
          "awslogs-region": "<your-aws-region>",
          "awslogs-create-group": "true",
          "awslogs-stream-prefix": "firelens"
        }
      },
      "secrets": [
        {
          "name": "APP_KEYS",
          "valueFrom": "arn:aws:secretsmanager:<region>:<account-id>:secret:<your-secret-name>:APP_KEYS::"
        },
        {
          "name": "API_TOKEN_SALT",
          "valueFrom": "arn:aws:secretsmanager:<region>:<account-id>:secret:<your-secret-name>:API_TOKEN_SALT::"
        },
        {
          "name": "ADMIN_JWT_SECRET",
          "valueFrom": "arn:aws:secretsmanager:<region>:<account-id>:secret:<your-secret-name>:ADMIN_JWT_SECRET::"
        },
        {
          "name": "TRANSFER_TOKEN_SALT",
          "valueFrom": "arn:aws:secretsmanager:<region>:<account-id>:secret:<your-secret-name>:TRANSFER_TOKEN_SALT::"
        },
        {
          "name": "DATABASE_CLIENT",
          "valueFrom": "arn:aws:secretsmanager:<region>:<account-id>:secret:<your-secret-name>:DATABASE_CLIENT::"
        },
        {
          "name": "DATABASE_FILENAME",
          "valueFrom": "arn:aws:secretsmanager:<region>:<account-id>:secret:<your-secret-name>:DATABASE_FILENAME::"
        },
        {
          "name": "JWT_SECRET",
          "valueFrom": "arn:aws:secretsmanager:<region>:<account-id>:secret:<your-secret-name>:JWT_SECRET::"
        }
      ]
    }
  ],
  "taskRoleArn": "arn:aws:iam::<account-id>:role/ecs-task-role",
  "executionRoleArn": "arn:aws:iam::<account-id>:role/ecs-task-role",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512"
}

Step 5: Fix the Public IP Address Issue

In some cases, you might face an issue where your Fargate task is not accessible via a public IP address. To resolve this issue, refer to this Stack Overflow answer:

Task running in Fargate is not found with public IP address

Follow the solution provided, which involves updating your Fargate service’s networking configuration to ensure your tasks are assigned public IP addresses.

Conclusion:

Congratulations! You have successfully integrated your Dockerized Strapi application with AWS Fargate and GitHub Actions for continuous integration and continuous deployment. Now, whenever you push changes to your GitHub repository, your application will automatically be deployed to AWS Fargate.

By adopting CI/CD best practices, you can streamline your development process, improve the stability of your application, and reduce the time it takes to deliver new features to your users. Happy coding!

References: