Automating Deployment to Private AWS Instances with GitHub Actions
Manually pushing code, building Docker images, and deploying applications to servers is a tedious process. Continuous Integration and Continuous Delivery/Deployment (CI/CD) pipelines offer a solution by automating these steps, streamlining the software development lifecycle. This article details how to build such a pipeline using GitHub Actions to deploy applications to an AWS EC2 instance located in a private subnet, inaccessible directly from the internet.
The Challenge: Deploying to Inaccessible Servers
The previous setup involved an EC2 instance residing in a private subnet. This is a sound security practice, shielding the server from direct public access. However, it creates a deployment hurdle. Every time code changes, developers must manually log into the instance, pull the latest code, and rebuild Docker images. This manual intervention is error-prone and significantly slows down the development cycle.
Introducing CI/CD with GitHub Actions
CI/CD aims to automate the build, test, and deployment phases of software development. GitHub Actions provides a powerful framework for creating custom automation workflows directly within GitHub repositories. By leveraging GitHub Actions, we can transform the manual deployment process into an automated, reliable workflow.
Leveraging AWS Systems Manager (SSM) for Private Instance Access
Since the EC2 instance is in a private subnet, direct SSH access from GitHub Actions is not feasible. AWS Systems Manager (SSM) provides a secure and auditable way to manage EC2 instances without opening inbound ports. SSM Agent, installed on the EC2 instance, allows us to run commands remotely through the AWS API. This bypasses the need for direct network connectivity from the CI/CD runner to the instance.
Secure Authentication with OpenID Connect (OIDC)
Traditionally, granting CI/CD systems access to cloud resources involved managing static AWS access keys and secret keys. These credentials, if compromised, pose a significant security risk. OpenID Connect (OIDC) offers a more secure alternative. GitHub Actions can be configured to exchange an OIDC identity token for temporary AWS credentials. This eliminates the need to store long-lived AWS secrets in GitHub. When a workflow runs, GitHub generates a short-lived OIDC token that the AWS IAM OIDC provider trusts. IAM roles can then be configured to trust this OIDC provider, granting the workflow temporary, role-based access to AWS resources.
Building the GitHub Actions Workflow
The CI/CD workflow will typically consist of several jobs:
1. Build and Test Job
This job runs on a GitHub Actions runner. It checks out the code, sets up the necessary build environment (e.g., Node.js, Python), installs dependencies, and runs automated tests. If tests fail, the workflow stops here, preventing faulty code from progressing.
2. Containerization Job (Optional but Recommended)
If the application is containerized, this job builds the Docker image. It uses a Dockerfile to create the image, tags it appropriately (e.g., with the Git commit SHA), and pushes it to a container registry like Amazon Elastic Container Registry (ECR) or Docker Hub.
3. Deployment Job
This is the core of the deployment process. It also runs on a GitHub Actions runner. This job will:
- Obtain AWS Credentials: Authenticate with AWS using OIDC. The workflow requests temporary credentials by exchanging its OIDC token with the IAM OIDC provider.
- Configure SSM: Use the obtained AWS credentials to interact with AWS Systems Manager.
- Execute Deployment Commands: Run SSM commands on the target EC2 instance. These commands might include:
- Pulling the latest Docker image from the registry.
- Stopping the currently running container.
- Starting a new container with the updated image.
- Running database migrations if necessary.
Workflow Trigger and Configuration
The workflow can be triggered on various events, such as a push to the `main` branch or the creation of a pull request. The workflow file (e.g., `deploy.yml`) is written in YAML and resides in the `.github/workflows` directory of the repository.
Key components of the workflow file include:
name: The name of the workflow.on: The events that trigger the workflow.jobs: Defines the tasks to be executed. Each job can have multiple steps.steps: Individual actions or commands within a job. This includes actions for checking out code, setting up environments, running scripts, and interacting with AWS.
IAM Role Configuration for OIDC
To enable OIDC, you need to:
- Create an IAM OIDC Provider: In AWS IAM, create an OIDC identity provider that points to `https://token.actions.githubusercontent.com`.
- Create an IAM Role: Create an IAM role that trusts the GitHub OIDC provider. Specify the GitHub repository and environment (e.g., `main` branch) that can assume this role.
- Attach Policies: Grant this role the necessary permissions. This typically includes permissions to interact with SSM (`ssm:SendCommand`) and potentially ECR (`ecr:GetAuthorizationToken`, `ecr:BatchCheckLayerAvailability`, `ecr:GetDownloadUrlForLayer`, `ecr:BatchGetImage`) if using ECR for container images.
SSM Command Structure
The `aws ssm send-command` API call is central to the deployment job. It specifies the instance ID, the command document to run (e.g., `AWS-RunShellScript`), and the actual commands to execute on the remote instance. These commands are defined as parameters within the `send-command` request.
Referenced Sources
- verified
