DEV Community

Anupa Supul
Anupa Supul

Posted on

What Happens When You Push Code to Production? Understanding AWS CI/CD with GitHub Actions, Docker, ECR & ECS

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.

What Happens When You Push Code to Production? AWS CI/CD with GitHub Actions, Docker, ECR, ECS, and Fargate

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

A workflow is defined in a YAML file inside the repository, usually under:

.github/workflows/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

For example, we might tag images using the Git commit SHA:

my-backend:a1b2c3d
my-backend:d4e5f6a
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then:

Task 1  ── Version 2
Task 2  ── Version 2
Task 3  ── Version 1
Enter fullscreen mode Exit fullscreen mode

Finally:

DEPLOYMENT COMPLETE

Task 1  ── Version 2
Task 2  ── Version 2
Task 3  ── Version 2
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This diagram shows the deployment pipeline and the running application, but they are two different flows.

Deployment flow:

Code → GitHub Actions → Docker → ECR → ECS
Enter fullscreen mode Exit fullscreen mode

Application request flow:

Users → ALB → ECS Tasks → RDS / S3 / Other Services
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)