Imagine you have finished building a Node.js backend. Everything works on your laptop, and you are ready to deploy it.
Traditionally, you might connect to a server, pull the latest code, install dependencies, restart the application, and hope nothing breaks.
But what happens when you need to do this every week? Every day? Or several times a day?
Manually repeating those steps becomes slow, error-prone, and difficult to manage.
This is where CI/CD becomes important.
After learning Docker, Amazon ECR, ECS, and Fargate, the next question is how to connect them into an automated deployment pipeline.
Let's follow a simple example: deploying a Node.js application to AWS whenever new code is pushed to GitHub.
1. First, What Is CI/CD?
CI/CD is a set of practices that helps developers build, test, and deliver software more reliably.
Continuous Integration (CI) means regularly integrating code changes and automatically checking whether the application still works.
Continuous Delivery (CD) means keeping the application ready for release. With Continuous Deployment, changes that pass the required checks can be deployed automatically to production.
Think about a team working on the same backend.
One developer fixes a login bug. Another adds a new API endpoint. Someone else updates the database integration.
Without automation, every change needs manual checking and deployment.
With CI/CD, a pipeline can automatically test the changes, build the application, package it into a Docker image, and deploy a new version.
The basic idea is:
Write code → Test it → Build it → Deploy it
2. The Complete AWS CI/CD Architecture
Here is the architecture we will use throughout this article.
DEVELOPER
|
v
GitHub
|
v
GitHub Actions
|
+------+------+
| |
v v
Install Run Tests
Dependencies |
| |
+------+------+
|
v
Build Docker Image
|
v
Amazon ECR
(Image Registry)
|
v
Amazon ECS
|
v
AWS Fargate
|
v
Running Tasks
|
v
Production App
This pipeline separates the deployment process into clear responsibilities.
- GitHub stores the source code.
- GitHub Actions automates the workflow.
- Docker packages the application.
- Amazon ECR stores the container image.
- Amazon ECS manages the deployment and running tasks.
- AWS Fargate runs the containers without requiring us to manage the underlying EC2 servers.
Each component solves a different problem.
3. Step One: Push Code to GitHub
Let's say we update our backend.
We add a new endpoint, fix a bug, or improve an existing feature.
After committing the changes, we push them to GitHub.
Developer
|
v
Local Code Changes
|
v
Git Commit
|
v
Git Push
|
v
GitHub Repository
GitHub becomes the starting point for our deployment workflow.
But pushing code to GitHub does not automatically mean that the code is safe to deploy. We still need to check it.
That is the responsibility of the next stage.
4. Step Two: GitHub Actions Runs the Pipeline
GitHub Actions is a CI/CD automation platform built into GitHub.
We can define a workflow that runs when someone pushes code to a particular branch or opens a pull request.
For example:
Code pushed to main
|
v
GitHub Actions starts
|
v
Install dependencies
|
v
Run automated tests
|
v
Continue if checks pass
A workflow is defined in a YAML file inside the repository, usually under:
.github/workflows/
A simplified workflow might look like this:
name: Backend CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
This example focuses on continuous integration. It checks the code and runs tests, but it does not yet build or deploy the Docker image.
If the tests fail, we can configure the pipeline to stop before deployment.
That is important because automation should prevent broken code from reaching production, not just deploy faster.
5. Step Three: Build the Docker Image
Once the required checks pass, the next stage is packaging the application.
This is where Docker connects with the pipeline.
Source Code
|
v
Dockerfile
|
v
Docker Build
|
v
Docker Image
The Docker image contains the application code, its dependencies, and the instructions needed to run it.
For example, a Node.js backend might use a Dockerfile like this:
FROM node:22
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
This is a simplified example. A production Dockerfile should also consider security, image size, dependency management, and which files should be excluded using .dockerignore.
The important point is that the pipeline can build a new image whenever the application changes.
Instead of manually preparing a server for every release, we produce a consistent application package.
6. Step Four: Push the Image to Amazon ECR
Now we have a Docker image. Where should it go?
Amazon Elastic Container Registry (ECR) is AWS's managed container image registry.
GitHub Actions can authenticate with AWS, build the image, tag it, and push it to an ECR repository.
GitHub Actions
|
v
Docker Image
|
v
Amazon ECR
|
v
Versioned Image
For example, we might tag images using the Git commit SHA:
my-backend:a1b2c3d
my-backend:d4e5f6a
Each tag identifies a particular build. This makes it easier to identify which version is running and to deploy a previous version when needed.
For production, avoid relying only on the mutable latest tag. Use explicit version tags and, where appropriate, immutable image digests.
One important security detail: GitHub Actions needs permission to access ECR. A recommended approach is to use AWS IAM roles with GitHub Actions OpenID Connect (OIDC), so the workflow can obtain temporary AWS credentials without storing long-lived AWS access keys in GitHub secrets.
7. Step Five: Deploy the New Image to Amazon ECS
The image is now stored in ECR, but it is not running yet.
We need ECS to deploy it.
Amazon ECS manages our container workloads. An ECS task definition describes how a container should run, including its image, CPU, memory, ports, and configuration.
When we deploy a new application version, the pipeline can register or reference an updated task definition and ask the ECS service to deploy it.
New Docker Image
|
v
Amazon ECR
|
v
Updated Task Definition
|
v
Amazon ECS Service
|
v
New Running Tasks
ECS then manages the deployment according to the service's configuration.
If we use Fargate, AWS manages the underlying compute infrastructure for the tasks.
We still need to configure the task's networking, IAM permissions, resource requirements, logging, and other application settings.
The benefit is that we no longer need to manually log into an EC2 instance to install the application and restart the process after every release.
8. Step Six: How Does the New Version Reach Users?
Imagine our backend currently has three running tasks:
ECS SERVICE — VERSION 1
Task 1 ── Version 1
Task 2 ── Version 1
Task 3 ── Version 1
Now we deploy Version 2.
ECS can use a rolling deployment to gradually replace the old tasks with new ones, according to the service's deployment settings.
DEPLOYMENT IN PROGRESS
Task 1 ── Version 2
Task 2 ── Version 1
Task 3 ── Version 1
Then:
Task 1 ── Version 2
Task 2 ── Version 2
Task 3 ── Version 1
Finally:
DEPLOYMENT COMPLETE
Task 1 ── Version 2
Task 2 ── Version 2
Task 3 ── Version 2
An Application Load Balancer can route requests to healthy registered targets while the deployment progresses.
However, a rolling deployment does not automatically guarantee zero downtime. That depends on settings such as health checks, minimum healthy capacity, maximum deployment capacity, and whether the new application version is compatible with existing clients and data.
This is why deployment configuration matters just as much as the pipeline itself.
9. What If the New Deployment Fails?
Suppose Version 2 contains a bug.
The application starts, but an API endpoint returns errors.
A reliable deployment process should not assume that a successful Docker build means the application is healthy.
We need checks at different stages:
- Automated tests before building or deploying.
- Container startup and health checks.
- ECS service and deployment status.
- Application logs and metrics.
- Post-deployment smoke tests that verify important endpoints.
ECS can replace unhealthy tasks, but replacing a task is not the same as detecting every application bug. A task might be running while its business logic is broken.
For deployments that fail, ECS deployment circuit breaker settings can help detect failures and optionally roll back to a previous deployment. The pipeline can also stop or trigger further action when post-deployment checks fail.
The goal is to detect problems quickly and reduce the impact on users.
10. CI/CD Is More Than Just Deployment
A common misconception is that CI/CD simply means automatically uploading code to a server.
A good pipeline should help us deliver changes safely and consistently.
A more complete workflow might look like this:
Developer Pushes Code
|
v
Automated Tests
|
v
Code / Security Checks
|
v
Build Docker Image
|
v
Push Image to ECR
|
v
Deploy to ECS
|
v
Health / Smoke Tests
|
v
Production
Depending on the project, we might also include dependency vulnerability scanning, infrastructure validation, manual approval before production, and notifications when a deployment succeeds or fails.
Not every project needs the same pipeline. The right setup depends on the application's risk, team size, compliance requirements, and deployment frequency.
11. What About Secrets and Permissions?
Automation needs access to AWS resources, but giving a pipeline unrestricted access is dangerous.
For example, GitHub Actions may need to push images to ECR and update an ECS service.
It should not automatically receive permission to modify every resource in the AWS account.
A safer design uses:
- IAM: Grants only the permissions the workflow needs.
- GitHub OIDC: Allows GitHub Actions to assume an AWS role using temporary credentials.
- Secrets Manager or Parameter Store: Stores application secrets and configuration where appropriate.
- ECS task roles: Allow the running application to access specific AWS services.
- Security Groups and VPC networking: Restrict network communication between the load balancer, application tasks, and database.
It is also important to distinguish the deployment pipeline's permissions from the permissions of the running application. They serve different purposes and should be configured separately.
12. The Complete Deployment Architecture
Let's connect the pieces.
DEVELOPER
|
v
GitHub
|
v
GitHub Actions
|
+---------+---------+
| |
v v
Run Tests Build Image
|
v
Amazon ECR
|
v
Amazon ECS
|
v
AWS Fargate
|
v
Running ECS Tasks
|
v
ALB
|
v
USERS
This diagram shows the deployment pipeline and the running application, but they are two different flows.
Deployment flow:
Code → GitHub Actions → Docker → ECR → ECS
Application request flow:
Users → ALB → ECS Tasks → RDS / S3 / Other Services
In a complete web application, Route 53 and CloudFront can sit in front of the application, while networking, security, monitoring, and scaling support the system.
The CI/CD pipeline changes how we deliver application versions. It doesn't replace the architecture that serves user requests.
13. GitHub Actions vs AWS CodePipeline
One question worth asking is whether we must use AWS CodePipeline.
No.
GitHub Actions can handle CI/CD workflows directly from a GitHub repository. It can run tests, build Docker images, push them to ECR, and trigger ECS deployments.
AWS CodePipeline is another option for orchestrating release workflows using AWS services.
The choice depends on the existing development workflow and operational requirements.
| GitHub Actions | AWS CodePipeline |
|---|---|
| Integrated with GitHub repositories | Integrated with AWS deployment workflows |
| Uses workflow YAML files | Uses pipeline stages and actions |
| Can build and deploy to AWS | Can orchestrate AWS and supported external actions |
| Convenient when source code is already on GitHub | Useful when a team wants an AWS-centered pipeline |
Neither is automatically the better choice. A team should consider its existing tools, access controls, deployment process, and maintenance needs.
The Mental Model I Keep Now
Before learning CI/CD, my deployment mental model was:
Write Code
|
v
Deploy Manually
|
v
Restart Application
Now I think about the process as:
Code
|
v
GitHub
|
v
Automated Checks
|
v
Docker Image
|
v
Amazon ECR
|
v
Amazon ECS
|
v
AWS Fargate
|
v
Production
Each stage has a responsibility.
GitHub stores the code. GitHub Actions automates the workflow. Docker packages the application. ECR stores the image. ECS manages the deployment. Fargate runs the containers.
That is the connection between development and production.
Quick Cheat Sheet
| Technology | Main Responsibility |
|---|---|
| GitHub | Source code management |
| GitHub Actions | CI/CD workflow automation |
| CI | Integrate changes and run checks |
| CD | Prepare or deliver changes to production |
| Docker | Package the application |
| ECR | Store container images |
| ECS | Manage container workloads and deployments |
| Task Definition | Describes how a task should run |
| ECS Service | Maintains desired tasks and manages deployments |
| Fargate | Runs containers without managing EC2 hosts |
| ALB | Routes requests to healthy targets |
| CloudWatch | Logs, metrics, dashboards, and alarms |
| IAM + OIDC | Control pipeline access to AWS |
Final Thoughts
The biggest lesson for me is that deploying an application shouldn't depend on someone remembering a long list of manual steps.
A well-designed CI/CD pipeline makes those steps repeatable, testable, and easier to monitor.
It also connects the tools developers use every day with the AWS services that run their applications in production.
Docker taught me how to package an application. ECR gave that package a place to live. ECS and Fargate showed me how to run it on AWS.
CI/CD connects all of them into a repeatable delivery process.
And that's an important step toward understanding not just cloud infrastructure, but how modern software is delivered and operated.
The next time you push code to GitHub, think about everything that could happen automatically before that code reaches your users.
Code → Test → Build → Package → Deploy → Verify.

Top comments (0)