Deploying an application to the cloud for the first time can feel overwhelming.
There are containers, container registries, networking, IAM, databases, security groups, task definitions, services, and many other concepts that need to work together.
This article documents my first end-to-end AWS deployment.
I took a FastAPI backend running locally, containerized it with Docker, pushed the image to Amazon ECR, deployed it using Amazon ECS with AWS Fargate, and connected it to PostgreSQL running on Amazon RDS.
The goal wasn't simply to get an application running on AWS.
I wanted to understand what actually happens when a locally developed backend becomes a cloud-hosted application.
Starting Point: A FastAPI Application
The application started as a normal FastAPI backend running on my local machine.
The basic architecture was:
Client
│
▼
FastAPI
│
▼
PostgreSQL
Locally, I could start the application with Uvicorn and connect it to a PostgreSQL database running on my machine.
This is convenient for development, but eventually the application needs to run somewhere other than my laptop.
That raised the first question:
How do I move this application to AWS?
Step 1 — Containerizing the Application
The first step was to package the FastAPI application into a Docker container.
Instead of depending on my local Python installation, virtual environment, and manually installed packages, Docker allows the application and its dependencies to be packaged together.
The conceptual architecture becomes:
FastAPI Application
│
▼
Dockerfile
│
▼
Docker Image
The Docker image contains the application code, Python environment, dependencies, and the instructions required to start the application.
This gives us a consistent artifact that can be moved between environments.
Instead of asking:
"Does this server have the correct Python version and dependencies?"
we can ask:
"Can this server run the Docker image?"
That is a much simpler deployment model.
Step 2 — Amazon ECR
Once the Docker image was created locally, the next problem was:
Where should the image be stored?
This is where Amazon Elastic Container Registry (ECR) comes in.
ECR is a managed container registry provided by AWS.
The workflow becomes:
Developer Laptop
│
│ docker build
▼
Docker Image
│
│ docker push
▼
Amazon ECR
ECR acts as the place where AWS can retrieve the container image when deploying the application.
The important distinction is:
ECR stores the image.
It does not run the application.
Step 3 — Amazon ECS
After storing the image in ECR, we need something that can actually run it.
This is where Amazon Elastic Container Service (ECS) comes in.
ECS is a container orchestration service.
It manages the deployment and operation of containers.
Conceptually:
Amazon ECR
│
│ Pull Image
▼
Amazon ECS
│
▼
Container
However, ECS itself does not necessarily provide the underlying compute capacity.
That brings us to Fargate.
Step 4 — AWS Fargate
AWS Fargate allows ECS containers to run without us having to manage EC2 servers ourselves.
With a traditional EC2-based deployment, we would need to manage:
EC2
├── Operating System
├── Docker
├── Security Updates
├── Container Runtime
└── Application
With Fargate, AWS manages the underlying compute infrastructure.
We primarily define:
- Which container image should run
- How much CPU it needs
- How much memory it needs
- Which ports it uses
- Which IAM permissions it requires
- Which networking configuration it should use
The architecture becomes:
Amazon ECR
│
▼
ECS Task
│
▼
AWS Fargate
│
▼
FastAPI Container
This was one of the important distinctions I learned:
ECS manages the containers, while Fargate provides the serverless compute capacity for those containers.
Step 5 — Connecting PostgreSQL with Amazon RDS
The FastAPI application also needed a database.
Instead of continuing to use PostgreSQL running on my laptop, I moved the database to Amazon RDS.
Amazon RDS is a managed relational database service.
The architecture now looked like:
AWS
│
┌──────┴──────┐
│ │
▼ ▼
ECS Fargate RDS
│ PostgreSQL
│
FastAPI
The FastAPI container communicates with PostgreSQL over the AWS network.
This introduced an important networking concept.
The application and database don't simply communicate because they are both "inside AWS."
They need appropriate networking and security configuration.
Understanding the Network
This was one of the most important parts of the deployment.
A simplified architecture looked like:
Internet
│
▼
AWS Networking
│
▼
ECS Fargate
│
│ PostgreSQL connection
▼
RDS PostgreSQL
The ECS task and RDS instance need to be able to communicate with each other through the configured network.
This means understanding concepts such as:
- VPC
- Subnets
- Security Groups
- IP addresses
- Ports
- Inbound rules
- Outbound rules
For PostgreSQL, the default port is:
5432
The database security configuration therefore needs to allow the appropriate application traffic to reach that port.
This was a good reminder that deploying an application isn't simply about putting a Docker container somewhere.
The components need to be able to communicate securely.
Security Groups
Security Groups act as virtual firewalls for AWS resources.
For example, the RDS database should not simply allow PostgreSQL traffic from the entire Internet.
Instead, the goal is to allow only the traffic that actually needs access to the database.
Conceptually:
Internet
X
│
│ PostgreSQL traffic
│
▼
RDS PostgreSQL
Only trusted application traffic
should be allowed to reach port 5432.
This follows an important security principle:
Allow only the traffic that is actually required.
IAM and Permissions
Another major part of the deployment was IAM — Identity and Access Management.
AWS resources should not simply have unrestricted access to everything else.
Instead, AWS uses identities and policies to determine what actions are permitted.
For example, the ECS task may need permission to:
- Pull an image from ECR
- Write logs to CloudWatch
- Access other AWS services when required
This is where IAM roles become important.
Rather than embedding AWS credentials inside the application container, AWS allows services to assume IAM roles.
Conceptually:
ECS Task
│
▼
IAM Role
│
▼
AWS Permissions
This is much safer than putting long-lived access keys directly inside the application.
Task Definitions
One of the concepts I had to understand while working with ECS was the Task Definition.
A task definition describes how an ECS task should run.
It specifies information such as:
- Container image
- CPU
- Memory
- Port mappings
- Environment variables
- IAM roles
- Logging configuration
Conceptually:
Task Definition
│
├── Container Image
├── CPU
├── Memory
├── Ports
├── Environment
└── IAM Configuration
ECS uses this definition to know how to launch the container.
ECS Service
A task is a running instance of the container configuration.
An ECS Service manages the desired number of running tasks.
For example:
ECS Service
Desired Tasks = 1
│
▼
FastAPI Task
If we later configure:
Desired Tasks = 3
ECS can maintain multiple running tasks.
This is where ECS begins to become useful for building scalable containerized applications.
Environment Variables
The application also needs configuration such as:
DATABASE_HOST
DATABASE_PORT
DATABASE_NAME
DATABASE_USER
DATABASE_PASSWORD
These values should not be hardcoded into the application.
Instead, the container receives configuration through its runtime environment.
This creates another useful separation:
Application Code
+
Environment Configuration
The same Docker image can therefore be deployed to different environments with different configuration.
The Complete Deployment Flow
After putting all the pieces together, the deployment flow becomes:
Developer
│
▼
FastAPI Application
│
▼
Docker Build
│
▼
Docker Image
│
▼
Amazon ECR
│
▼
Amazon ECS
│
▼
AWS Fargate
│
▼
FastAPI Container
│
▼
Amazon RDS
│
▼
PostgreSQL
Each component has a specific responsibility.
Docker
→ Packages the application
ECR
→ Stores the Docker image
ECS
→ Manages the container workload
Fargate
→ Provides serverless compute for the container
RDS
→ Runs managed PostgreSQL
IAM
→ Controls permissions
VPC / Security Groups
→ Control network communication
What Made This Deployment Difficult?
The difficult part wasn't writing the FastAPI application.
The challenging part was understanding how all the infrastructure pieces fit together.
A locally running application can feel deceptively simple:
FastAPI
│
▼
PostgreSQL
In AWS, the architecture becomes much more explicit:
AWS
│
┌──────┴──────┐
│ │
▼ ▼
ECS/Fargate RDS
│ PostgreSQL
│
FastAPI
│
▼
Network + IAM
Every connection has to be intentionally configured.
The deployment therefore became an exercise in understanding infrastructure rather than simply moving code from one machine to another.
The Biggest Lessons
This was my first end-to-end deployment of a FastAPI backend to AWS, and several concepts became much clearer after actually building it.
1. Docker separates the application from the machine
The application becomes a portable artifact rather than a collection of files that must be manually configured on every server.
2. ECR and ECS have different responsibilities
ECR stores container images.
ECS runs and manages containers.
They solve different problems.
3. Fargate removes server management
I don't need to manually manage an EC2 instance just to run my Docker container.
4. RDS separates database infrastructure from the application
The FastAPI application doesn't need to run PostgreSQL itself.
AWS manages the database infrastructure through RDS.
5. Networking is part of application deployment
A backend and database don't communicate automatically just because they are both hosted on AWS.
VPC configuration, subnets, routes, security groups, ports, and connectivity all matter.
6. IAM is fundamental
Cloud applications need controlled access between services.
IAM roles and policies provide that control without requiring applications to store long-lived credentials.
From Localhost to AWS
The most interesting part of this project was seeing how familiar local development concepts map to AWS infrastructure.
Locally:
Laptop
│
├── FastAPI
│
└── PostgreSQL
In AWS:
AWS
│
├── ECS + Fargate
│ └── FastAPI Container
│
└── RDS
└── PostgreSQL
And the container image moves through:
Local Docker
│
▼
Amazon ECR
│
▼
ECS + Fargate
The application itself didn't fundamentally change.
The infrastructure around it did.
That was probably the biggest lesson from this deployment.
Final Thoughts
Before working on this project, services such as ECR, ECS, Fargate, RDS, IAM, and VPC felt like separate AWS concepts.
After deploying the application, they started to make sense as pieces of one larger system.
A cloud deployment isn't simply:
"Put the application on AWS."
It is a collection of decisions about:
- Where the application runs
- How it is packaged
- Where the image is stored
- How containers are managed
- Where the database lives
- How services communicate
- Which permissions they have
- How network traffic is controlled
This deployment gave me my first practical understanding of how those pieces fit together.
And more importantly, it showed me that moving from local development to the cloud is not just about learning AWS services.
It is about learning how applications, infrastructure, networking, security, and deployment processes work together.
Conclusion
This was my first end-to-end AWS deployment of a Dockerized FastAPI backend.
The final stack consisted of:
FastAPI
│
▼
Docker
│
▼
Amazon ECR
│
▼
Amazon ECS
│
▼
AWS Fargate
│
▼
PostgreSQL
│
▼
Amazon RDS
Along the way, I learned about Docker images, container registries, ECS task definitions, Fargate, RDS, IAM, VPC networking, security groups, environment configuration, and the overall deployment lifecycle.
For me, this was the point where AWS stopped feeling like a list of services and started feeling like an actual infrastructure platform.
Top comments (0)