Deploy a FastAPI App to ECS Fargate

Deploy a containerized FastAPI app to AWS ECS Fargate with a CI/CD pipeline.

Introduction

30 Second Summary

You've built an app that works perfectly on your machine. But "works on my machine" doesn't help anyone else use it. To share your app with the world, you need to deploy it to the cloud.

In this project, you will deploy your existing FastAPI application to AWS using Amazon ECS Fargate, a serverless container platform. You'll push your Docker image to Amazon ECR, configure your container with environment variables for your API keys, and get a public URL so anyone can access your app.

What You'll Build

A live FastAPI application running on AWS ECS Fargate, backed by a CI/CD pipeline using GitHub Actions and Amazon ECR.

By the end of this project, you'll have:

  • An Amazon ECR repository storing your Docker image, pushed automatically by GitHub Actions.
  • An ECS Fargate cluster running your FastAPI container with a public IP address anyone can access.
  • An ECS task definition with your API keys configured as environment variables.
  • Secret Mission: Set up auto-deploy so every push to main automatically deploys a new version to ECS.

Are there any prerequisites?

This project assumes you already have a FastAPI application with a working Dockerfile, a GitHub repository with GitHub Actions already set up for Docker builds, and a GitHub account. If you don't have these yet, complete the earlier projects in this series first.

Set Up Your AWS Account and CLI

Your FastAPI app runs on your machine, but nobody else can access it yet. To deploy it to the cloud, you'll use AWS.

This step gets your AWS environment ready. You'll create an account, install the AWS CLI, and set up an IAM user that GitHub Actions can use to deploy containers on your behalf.

In this step, get ready to:
  • Create an AWS account and select the right region.
  • Install and verify the AWS CLI on your machine.
  • Create an IAM user with deployment permissions for GitHub Actions.
Create an AWS account

AWS is the cloud platform that will host your container. You need an account before you can create any resources.

  • Open your browser and go to https://aws.amazon.com.
  • Click Create an AWS Account in the top-right corner.
  • Follow the sign-up flow. You will need an email address, a phone number, and a credit card for identity verification.
  • When asked to select a support plan, choose Basic Support - Free.
  • Complete the sign-up and sign in to the AWS Management Console.

Do I need a credit card?

Yes. AWS requires a credit card for identity verification when creating a new account. You will not be charged for free tier services. This project uses Fargate, which costs approximately $0.03 per hour for the smallest configuration. Full teardown instructions are included in the Before You Go section.

AWS has data centers all over the world, organized into regions. For this project, you need to be in the us-east-1 region.

  • Look at the top-right corner of the AWS Console, next to your account name. You should see a region name.
  • Click the region dropdown.
  • Select US East (N. Virginia) us-east-1.

Why us-east-1?

The us-east-1 region is the most commonly used AWS region. It tends to get new features first and has the widest selection of services available.

Install the AWS CLI

The AWS CLI is a command-line tool that lets you interact with AWS services from your terminal. You need it installed so you can configure credentials that GitHub Actions will use later.

macOS

  • Download and install the AWS CLI by running these commands in your terminal:
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /

What do these commands do?

The first command downloads the official AWS CLI installer package from Amazon. The second command runs the macOS installer, which places the AWS CLI into /usr/local/aws-cli and creates a symlink in /usr/local/bin.

Your Mac will ask for your password

The sudo command requires your Mac's login password. Type it and press Enter. You will not see any characters as you type.

Windows

What does the installer do?

The MSI installer adds the aws command to your system PATH. After installation, you can run AWS CLI commands from any Command Prompt or PowerShell window.

After installation, close and reopen your terminal to make sure the new aws command is available.

  • Verify the installation by running this command:
aws --version

You should see output that starts with aws-cli/2. followed by version details.

✔️ I see a version number

The AWS CLI is installed and ready to go.

ⓧ Command not found

  • Make sure you closed and reopened your terminal after installing.
  • On Windows, try opening a new Command Prompt or PowerShell window.
  • On macOS, try opening a new terminal tab or window.
  • If it still does not work, re-run the installer from the platform-specific instructions above.

Help me troubleshoot why aws --version is not working after installing the AWS CLI.

Create an IAM user and configure the CLI

IAM (Identity and Access Management) controls who can do what in your AWS account. You need a dedicated IAM user that GitHub Actions will use to push Docker images and manage containers.

Why a separate IAM user?

Your root AWS account has full access to everything. Giving those credentials to a CI/CD pipeline is risky.

A dedicated IAM user with limited permissions follows the principle of least privilege. If the credentials are ever compromised, the damage is contained to only the services the user can access.

  • In the AWS Console, type IAM in the search bar at the top and select IAM from the results.
  • In the left sidebar, click Users.
  • Click Create user.
  • Enter github-actions-deployer as the user name.
  • Click Next.

Now you need to attach permissions that let this user manage ECS and ECR resources.

  • Select Attach policies directly.
  • In the search box, type AmazonECS_FullAccess and check the box next to it.
  • Clear the search box, type AmazonEC2ContainerRegistryFullAccess, and check the box next to it.
  • Clear the search box, type IAMReadOnlyAccess, and check the box next to it.
  • Click Next, then click Create user.

What do these policies allow?

  • AmazonECS_FullAccess lets the user create and manage ECS clusters, task definitions, and services.
  • AmazonEC2ContainerRegistryFullAccess lets the user push and pull Docker images from ECR.
  • IAMReadOnlyAccess lets the user read IAM role information, which ECS needs when deploying tasks.

Now you need to create an access key. This is how GitHub Actions will authenticate as this user.

  • Click on your newly created github-actions-deployer user in the users list.
  • Click the Security credentials tab.
  • Scroll down to Access keys and click Create access key.
  • Select Third-party service as the use case.
  • Check the confirmation box at the bottom and click Next.
  • Click Create access key.

Copy your credentials now

You will see an Access key ID and a Secret access key. Copy both values and save them somewhere safe, like a text file on your Desktop.

The Secret access key is only shown once. If you close this page without copying it, you will need to create a new access key.

With your credentials in hand, you can now configure the AWS CLI to use them.

  • Configure the AWS CLI by running this command:
aws configure

This command starts an interactive prompt with four questions. Enter the following values:

  • AWS Access Key ID: paste the Access Key ID you copied.
  • AWS Secret Access Key: paste the Secret Access Key you copied.
  • Default region name: type us-east-1.
  • Default output format: type json.

What does aws configure do?

This command saves your credentials and default settings to a file on your machine at ~/.aws/ (macOS) or %UserProfile%\.aws\ (Windows). Every time you run an aws command, the CLI reads these files to authenticate your requests.

  • Verify the configuration by running this command:
aws sts get-caller-identity

You should see a JSON response with your account ID, user ARN, and the user ID. The ARN should contain github-actions-deployer.

Seeing an error?

  • If you see InvalidClientTokenId, double-check that you pasted the Access Key ID correctly with no extra spaces.
  • If you see SignatureDoesNotMatch, the Secret Access Key was likely pasted incorrectly. Run aws configure again and re-enter both values.
  • If you see Unable to locate credentials, the aws configure step may not have completed. Run it again from the start.

Help me debug my aws sts get-caller-identity error.

Your AWS environment is fully set up. Next up, you'll create an ECR repository and push your first Docker image to it from GitHub Actions.

Create an ECR Repository and Push Your First Image

Your AWS account is ready and your CLI is connected. Now it's time to get your Docker image into AWS so that ECS can run it.

Amazon ECR (Elastic Container Registry) is a private container registry hosted inside your AWS account. You'll create a repository in ECR, then set up your GitHub Actions workflow to automatically build and push images there.

In this step, get ready to:
  • Create a private ECR repository to store your Docker images.
  • Add your AWS credentials as secrets in GitHub.
  • Build a GitHub Actions workflow that pushes images to ECR.
Create a private ECR repository

Your Docker image needs a home in AWS before ECS can run it. An ECR repository is that home.

  • In the AWS Console, type ECR in the top search bar.
  • Select Elastic Container Registry from the results.
  • Click Repositories in the left sidebar.
  • Click Create repository.
  • Select Private for the visibility settings.
  • Enter my-fastapi-app in the Repository name field.
  • Leave all other settings as their defaults.
  • Click Create repository.

After creation, you'll see your new repository listed. Note the URI column. It looks something like 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-fastapi-app.

What is Amazon ECR?

Amazon ECR is a managed Docker container registry. It stores your Docker images privately inside your AWS account.

When ECS needs to run your container, it pulls the image directly from ECR. Think of it like a private Docker Hub that lives alongside the rest of your AWS infrastructure.

Don't see your repository?

Make sure you are in the US East (N. Virginia) us-east-1 region. Check the region dropdown in the top-right corner of the console. ECR repositories are region-specific, so a repository created in a different region will not appear here.

Why can't I see my ECR repository?

Push your Docker image from GitHub Actions

Your GitHub Actions workflow needs permission to push images to your AWS account. You'll store your IAM user's access keys as encrypted secrets in GitHub, then create a workflow file that builds and pushes your image to ECR.

  • Open your GitHub repository in your browser.
  • Click Settings in the top navigation bar of your repository.
  • In the left sidebar, click Secrets and variables.
  • Click Actions.
  • Click New repository secret.
  • Enter AWS_ACCESS_KEY_ID in the Name field.
  • Paste your IAM user's Access Key ID in the Secret field.
  • Click Add secret.
  • Click New repository secret again.
  • Enter AWS_SECRET_ACCESS_KEY in the Name field.
  • Paste your IAM user's Secret Access Key in the Secret field.
  • Click Add secret.

You should now see both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY listed on the Actions secrets page.

Can't find Settings?

The Settings tab only appears for users with admin or owner permissions on the repository. Make sure you are viewing a repository that you own.

Why can't I see the Settings tab in my GitHub repository?

Now you'll create the workflow file that GitHub Actions runs on every push to main. This workflow authenticates with AWS, logs into ECR, builds your Docker image, and pushes it.

  • In your local project, create the folder .github/workflows/ if it doesn't already exist.
  • Create a new file at .github/workflows/deploy.yml and add the following code:
name: Deploy to Amazon ECS

# Trigger on every push to the main branch
on:
  push:
    branches:
      - main

# Reusable variables for the workflow
env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: my-fastapi-app
  ECS_SERVICE: fastapi-service
  ECS_CLUSTER: fastapi-cluster
  ECS_TASK_DEFINITION: .aws/task-definition.json
  CONTAINER_NAME: fastapi-container

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest

    steps:
      # Pull the latest code from your repository
      - name: Checkout
        uses: actions/checkout@v5

      # Authenticate with AWS using your GitHub secrets
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      # Log Docker into your private ECR registry
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      # Build the image, tag it with the git commit SHA, and push to ECR
      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

What does this workflow do?

  • on: push: branches: - main triggers the workflow whenever you push code to the main branch.
  • The env block defines reusable variables like your ECR repository name and AWS region. Some of these variables (like ECS_SERVICE and ECS_CLUSTER) will be used in a later step.
  • aws-actions/configure-aws-credentials@v4 authenticates the GitHub runner using the secrets you just added.
  • aws-actions/amazon-ecr-login@v2 logs Docker into your private ECR registry so it can push images.
  • The build step tags your image with the git commit SHA (github.sha). Every push creates a uniquely identifiable image.
  • Save deploy.yml.
  • Commit and push this file to the main branch of your repository.

This push automatically triggers the workflow. The GitHub Actions runner will build your Docker image and push it to ECR.

  • Go to the Actions tab in your GitHub repository.
  • Click on the latest workflow run to watch its progress.

Wait for all steps to show green checkmarks. This usually takes 2-5 minutes depending on your Docker image size.

Seeing a failed workflow run?

  • Click on the failed step to see the error logs. The most common issue is incorrect AWS credentials. Double-check that your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY secrets match the values from Step 1 exactly.
  • If you see a RepositoryNotFoundException error, make sure the ECR_REPOSITORY value in your workflow (my-fastapi-app) matches the exact name of the ECR repository you created.
  • If the Docker build fails, check that your Dockerfile is in the root of your repository. The workflow runs docker build . from the repo root.

Help me debug my failed GitHub Actions workflow for ECR.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete deploy.yml file. Make sure yours matches:

name: Deploy to Amazon ECS

on:
  push:
    branches:
      - main

env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: my-fastapi-app
  ECS_SERVICE: fastapi-service
  ECS_CLUSTER: fastapi-cluster
  ECS_TASK_DEFINITION: .aws/task-definition.json
  CONTAINER_NAME: fastapi-container

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v5

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
Verify your image in ECR

Once the workflow run completes with green checkmarks, confirm that the image actually landed in your ECR repository.

  • Go back to the AWS Console.
  • Navigate to Elastic Container Registry and click on your my-fastapi-app repository.

You should see your image listed with a long alphanumeric string as the tag. That string is the git commit SHA from your push.

Why tag with the git SHA?

Every git commit has a unique SHA (a long hash string). By tagging your Docker image with this SHA, you can always trace exactly which version of your code is running in any container.

This is a standard practice in CI/CD pipelines. It makes debugging and rollbacks much easier.

Don't see any images?

Go back to your GitHub repository's Actions tab and check whether the workflow run completed successfully. If the run is still in progress, wait for it to finish. If it failed, expand the failed step to see the error message.

Help me figure out why my image isn't showing up in ECR.

Your CI pipeline is working. Every time you push to main, GitHub Actions builds a fresh Docker image and stores it in ECR. Next up, you'll create the ECS infrastructure that actually runs this image as a live application.

Create an ECS Cluster and Task Definition

Your Docker image is stored in ECR, and GitHub Actions pushes a fresh build on every commit. Now it's time to tell AWS how to run that image as a live application.

Amazon ECS (Elastic Container Service) is AWS's container orchestration service. In this step, you'll create a cluster to house your containers and a task definition that describes exactly how your FastAPI container should run.

In this step, get ready to:
  • Create an ECS cluster using AWS Fargate.
  • Build a task definition with your container settings and API keys.
  • Export your task definition JSON to your repository.
Create an ECS cluster

An ECS cluster is a logical grouping for your container deployments. It's the workspace where your running services and tasks will live.

  • In the AWS Console, type ECS in the top search bar and select Elastic Container Service.
  • Click Clusters in the left sidebar.
  • Click Create cluster.
  • Enter fastapi-cluster in the Cluster name field.
  • Under Infrastructure, confirm that AWS Fargate (serverless) is already selected.
  • Leave all other settings at their defaults.
  • Click Create.

The cluster creation takes just a few seconds. You should see fastapi-cluster listed with a status of Active.

What is an ECS cluster?

A cluster is a logical grouping of tasks and services. It doesn't create any servers on its own.

With Fargate, AWS manages all the underlying compute infrastructure for you. You just define what to run, and Fargate handles the servers behind the scenes.

Don't see your cluster?

  • Make sure you are in the US East (N. Virginia) us-east-1 region. Check the region dropdown in the top-right corner of the console.
  • Try refreshing the page. The cluster list sometimes takes a moment to update.
  • Why isn't my ECS cluster showing up after creation?
Create a task definition

A task definition is a blueprint for your container. It tells ECS which Docker image to pull, how much CPU and memory to allocate, which ports to expose, and what environment variables to inject.

  • In the ECS left sidebar, click Task definitions.
  • Click Create new task definition.
  • Enter fastapi-task in the Task definition family field.

Now configure the infrastructure settings for your container.

  • Under Infrastructure requirements, select AWS Fargate.
  • Set CPU to .25 vCPU.
  • Set Memory to .5 GB.

Why the smallest config?

0.25 vCPU and 0.5 GB of memory is the smallest Fargate task size. It costs approximately $0.03 per hour, which is plenty for testing your FastAPI app.

Next, configure the container itself.

  • Scroll down to the Container - 1 section.
  • Enter fastapi-container in the Name field.
  • In the Image URI field, paste your ECR repository URI followed by :latest.

Where do I find my Image URI?

Go to the ECR console, click on your my-fastapi-app repository, and copy the URI shown at the top. It looks like 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-fastapi-app. Add :latest to the end so ECS always pulls the most recent image.

  • Set Container port to 8000.
  • Set Protocol to TCP.

Your FastAPI app needs API keys to connect to its external services. You'll add these as environment variables so the container can access them at runtime.

  • Scroll down to the Environment variables section.
  • Click Add environment variable.
  • Set Key to OPENAI_API_KEY and Value to your actual OpenAI API key.
  • Click Add environment variable again.
  • Set Key to REDIS_URL and Value to your actual Redis Cloud URL.
  • Click Add environment variable one more time.
  • Set Key to PINECONE_API_KEY and Value to your actual Pinecone API key.
  • Click Create.

You should see fastapi-task listed under Task definitions with an ACTIVE status.

What about ecsTaskExecutionRole?

When you create a task definition through the console, ECS automatically creates a task execution role called ecsTaskExecutionRole if it doesn't already exist. This role allows ECS to pull your Docker image from ECR and send container logs to CloudWatch.

Seeing an error when creating the task definition?

  • Double check that your Image URI follows the format 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-fastapi-app:latest. A missing tag or typo in the URI is the most common cause.
  • Confirm that the container port is set to 8000 and the protocol is TCP.
  • Help me troubleshoot my ECS task definition creation error.
Export your task definition to your repository

Your GitHub Actions workflow needs a copy of this task definition to deploy updates automatically. You'll save a template version of it as a JSON file in your repository.

The template replaces your actual image URI and API keys with placeholders. Your workflow will inject the correct image at deploy time, and your secrets stay out of version control.

  • In your project repository folder, create a new folder called .aws.
  • Inside .aws, create a new file called task-definition.json.
  • Paste the following JSON into .aws/task-definition.json:
{
  "family": "fastapi-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "fastapi-container",
      "image": "PLACEHOLDER",
      "portMappings": [
        {
          "containerPort": 8000,
          "protocol": "tcp"
        }
      ],
      "essential": true,
      "environment": [
        {
          "name": "OPENAI_API_KEY",
          "value": "your-openai-api-key-here"
        },
        {
          "name": "REDIS_URL",
          "value": "your-redis-cloud-url-here"
        },
        {
          "name": "PINECONE_API_KEY",
          "value": "your-pinecone-api-key-here"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/fastapi-task",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}

What does this JSON do?

  • family names this task definition so ECS can track revisions of it.
  • networkMode is set to awsvpc, which is required for Fargate. It gives your container its own network interface.
  • image is set to PLACEHOLDER because your GitHub Actions workflow will inject the real image URI during deployment.
  • environment lists the API keys your app needs. The values here are placeholders so your real secrets don't end up in version control.
  • logConfiguration sends your container's logs to CloudWatch, which you can use to debug issues once your app is running.
  • Save .aws/task-definition.json.
  • Commit and push the new file to your GitHub repository by running these commands:
git add .aws/task-definition.json
git commit -m "Add ECS task definition template"
git push origin main

Why not commit your real API keys?

Your real API keys are stored in the ECS task definition you created through the console. That's what your running container will use.

The JSON file in your repo is a template for your CI/CD pipeline. Committing real credentials to a Git repository is a security risk, even in a private repo.

  • Verify the file exists in your GitHub repository by navigating to the .aws folder on your repo's main page. You should see task-definition.json listed.

Push triggered a workflow run?

Because your workflow triggers on pushes to main, this commit will start a new workflow run. That's expected. The build and push steps will succeed, but the deploy steps (if present) may fail because you haven't created the ECS service yet. You'll set that up in the next step.

✔️ Awesome, I've got everything!

Great. Double check you've saved .aws/task-definition.json and pushed it to your repository.

ⓧ I'd like to double check the full code

Here is the complete .aws/task-definition.json file:

{
  "family": "fastapi-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "fastapi-container",
      "image": "PLACEHOLDER",
      "portMappings": [
        {
          "containerPort": 8000,
          "protocol": "tcp"
        }
      ],
      "essential": true,
      "environment": [
        {
          "name": "OPENAI_API_KEY",
          "value": "your-openai-api-key-here"
        },
        {
          "name": "REDIS_URL",
          "value": "your-redis-cloud-url-here"
        },
        {
          "name": "PINECONE_API_KEY",
          "value": "your-pinecone-api-key-here"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/fastapi-task",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}

And here is the full .github/workflows/deploy.yml (unchanged from the previous step):

name: Deploy to Amazon ECS

on:
  push:
    branches:
      - main

env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: my-fastapi-app
  ECS_SERVICE: fastapi-service
  ECS_CLUSTER: fastapi-cluster
  ECS_TASK_DEFINITION: .aws/task-definition.json
  CONTAINER_NAME: fastapi-container

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v5

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

Your cluster is running and your task definition is locked in. Next up, you'll create an ECS service that launches your container and gives it a public URL you can access from any browser.

Deploy Your App and Get a Public URL

Your ECS cluster is running and your task definition is ready. Now it's time to launch your container and get a live URL you can open from any browser.

An ECS service is the component that takes your task definition blueprint and actually runs it. It starts your Fargate task, keeps it running, and assigns it a public IP address so the world can reach your app.

In this step, get ready to:
  • Create an ECS service with a security group that allows traffic on port 8000.
  • Find your app's public IP address.
  • Test your FastAPI endpoints and verify your API integrations are working.
Create an ECS service

A service tells ECS to start and manage one or more copies of your task. If a task crashes, the service automatically replaces it.

  • In the AWS Console, navigate to Amazon Elastic Container Service using the search bar at the top.
  • Click Clusters in the left sidebar.
  • Click fastapi-cluster to open it.
  • Click the Services tab.
  • Click Create.

You'll see the service creation form. Start by configuring which task definition to run.

  • Under Deployment configuration, select fastapi-task from the Family dropdown.
  • Select the latest revision.
  • Enter fastapi-service in the Service name field.
  • Set Desired tasks to 1.

Next, configure the networking. Your container needs a public IP and a security group that allows traffic on port 8000.

What is a security group?

A security group acts as a virtual firewall for your container. It controls which network traffic is allowed in and out.

By default, the VPC's security group blocks inbound traffic on port 8000. You need to create a new security group that explicitly allows it, so browsers can reach your FastAPI app.

  • Scroll down to the Networking section.
  • Leave the default VPC selected.
  • Make sure at least one public subnet is selected.
  • In the Security group section, click Create a new security group.
  • Enter fastapi-sg as the Security group name.
  • Enter Allow inbound traffic on port 8000 as the Security group description.
  • Add an inbound rule with Type set to Custom TCP.
  • Set the Port range to 8000.
  • Set the Source to Anywhere (0.0.0.0/0).
  • Make sure the Public IP toggle is turned ON.
  • Leave all other settings as default.
  • Scroll to the bottom and click Create.

You should see a confirmation banner. Your service status shows as Active in the cluster's Services tab.

Service stuck in PROVISIONING?

Give it 2-3 minutes. ECS needs time to pull your Docker image from ECR and start the container.

If the task keeps failing, click the Tasks tab in your service, then click the stopped task to check its Stopped reason. Common causes include the task execution role missing permissions to pull the ECR image.

My ECS service task keeps failing to start.

Find your public URL

Once your task is running, ECS assigns it a public IP address. This is the URL you'll use to reach your FastAPI app from any browser.

  • In your cluster, click the Services tab.
  • Click fastapi-service.
  • Click the Tasks tab.
  • Wait until you see a task with a Last status of RUNNING. This can take 2-3 minutes.
  • Click on the running task ID.
  • In the Configuration section, find the Public IP address.
  • Copy the Public IP address.
  • Open a new browser tab and navigate to http://<your-public-ip>:8000, replacing <your-public-ip> with the IP address you copied.

You should see your FastAPI application running live on AWS. This is the same app you've been running locally, now accessible from anywhere in the world.

No Public IP showing?

Make sure the Public IP toggle was turned ON when you created the service. If it was off, you need to delete the service and recreate it with the toggle enabled.

Also confirm your task is running in a public subnet. Private subnets do not assign public IPs.

If you can see the IP but the page won't load, check that your security group has an inbound rule for TCP port 8000 from 0.0.0.0/0.

My ECS Fargate task has no public IP address or I can't reach it in the browser.

Test your application endpoints

Seeing the app load is a great sign. Now verify that the backend integrations with OpenAI, Redis Cloud, and Pinecone are actually working.

  • Try hitting your API endpoints through the browser or a tool like Postman.
  • Test a request that triggers the OpenAI integration to confirm your OPENAI_API_KEY environment variable is set correctly.
  • Test a request that uses Redis Cloud to verify your REDIS_URL is correct.
  • Test a request that queries Pinecone to verify your PINECONE_API_KEY is working.

What if something isn't working?

ECS streams your container logs to CloudWatch by default. These logs contain the same output you'd see if you ran the container locally.

To check them, go back to your running task in the ECS console and click the Logs tab. You'll see your FastAPI application's stdout and stderr output. Look for error messages related to missing API keys or connection failures.

Seeing 500 errors or connection failures?

The most common cause is a typo in your environment variables. Go to ECS > Task Definitions > fastapi-task and check that your OPENAI_API_KEY, REDIS_URL, and PINECONE_API_KEY values are correct.

If you update the task definition, you'll need to create a new revision and then update your service to use it. ECS will launch a new task with the corrected values.

My FastAPI app on ECS is returning 500 errors or can't connect to external APIs.

Your FastAPI app is live on AWS. In the Secret Mission, you'll take it one step further by automating the entire deployment pipeline so every push to main deploys a new version automatically.

Secret mission

Set Up Auto-Deploy on Push

Your CI pipeline pushes new images to ECR on every commit. But deploying those images still requires a manual update in the AWS Console. What if every push to main automatically rolled out a new version to your live ECS Fargate service?

Before you go

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

This project uses AWS resources that cost money while running. Your ECS Fargate task costs approximately $0.03 per hour, which adds up to about $22 per month if left running 24/7.

Cost warning

Your ECS Fargate task charges ~$0.72/day while running. If you are not actively using or testing your deployment, pause or delete your resources to avoid unexpected charges.

Resources you used:

  • IAM user (github-actions-deployer) with access keys.
  • Amazon ECR private repository (my-fastapi-app).
  • ECS cluster (fastapi-cluster).
  • ECS task definition (fastapi-task).
  • ECS service (fastapi-service).
  • Security group allowing inbound traffic on port 8000.
  • GitHub secrets (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY).
  • CloudWatch log group (/ecs/fastapi-task).

Keep everything running

No action needed. Choose this if you are still actively building or testing your deployment.

  • Your app stays live at the public IP address.
  • Ongoing cost is approximately $0.72/day (~$22/month) for the Fargate task.
  • ECR storage is free up to 500 MB for 12 months.

Pause - I'll come back to this later

Stop your running Fargate task to halt charges, but keep all resources in place so you can resume later.

  • In the AWS Console, go to Amazon ECS > Clusters > fastapi-cluster.
  • Click the Services tab, then select fastapi-service.
  • Click Update and set Desired tasks to 0.
  • Click Update to save. This stops the Fargate task and stops all compute charges.
  • When you want to resume, set Desired tasks back to 1.

Delete - I don't want to use this again

Remove all AWS resources and stop all charges. Follow these steps in order.

  • Go to Amazon ECS > Clusters > fastapi-cluster > Services. Select fastapi-service, click Update, and set Desired tasks to 0. Then delete the service.
  • Go to Amazon ECS > Clusters and delete fastapi-cluster.
  • Go to Amazon ECS > Task Definitions. Select fastapi-task and deregister all revisions.
  • Go to Amazon ECR > Repositories. Select my-fastapi-app and delete the repository.
  • Go to IAM > Users. Select github-actions-deployer and delete the user.
  • Go to CloudWatch > Log groups. Delete the /ecs/fastapi-task log group.
  • In your GitHub repository, go to Settings > Secrets and variables > Actions. Delete AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
  • Delete the custom security group you created for port 8000 in EC2 > Security Groups.

Mission accomplished

That's a Wrap!

Nice work! You just deployed your FastAPI application to the cloud using Amazon ECS Fargate, backed by a fully automated deployment pipeline.

You've learned how to:

  • Created an ECR private repository and pushed Docker images to it from GitHub Actions.
  • Built an ECS task definition with environment variables for your API keys and deployed your app to a Fargate cluster with a public IP address.
  • Configured an IAM user with programmatic access and set up the AWS CLI to manage your cloud resources from the terminal.
  • Secret Mission: Set up a full CI/CD pipeline that auto-deploys your app to ECS on every push to main.

Ready to quiz yourself?