Deploy a FastAPI Fullstack Template project to AWS EC2

Tutorial
A step-by-step guide to deploying a FastAPI fullstack template project to an EC2 instance.
Author

Niyazi Kemer

Published

December 26, 2024

Thanks to Sebastián Ramírez @tiangolo for this full-stack-fastapi-template I’m not a full stack developer(check my bio) or even a developer, yet this template help me a lot to create a full stack machine learning app for a company.

The template is very well documented and relatively easy to understand, but I still wanted to make this post and a companion video for first-timers like me or someone who wants a quick recipe. However, I still encourage you all to at least read the documentation of the template along with this post.

I assume your app work well on your local environment and you want to deploy it on the web. This is my take on deploying a project on EC2. The purpose of post is to document my process for later use and to share with the community.

*All Italics are copied from the original documentation

0- Install Docker Engine on the host computer

link

1 - DNS Record

The very first thing I needed to do was configure DNS records for my domain name.. It changes based on your domain name provider but all you need to do is then put your public IP address in the record and that’s all. It just directs your domain name to the public address of your EC2 instance.

In my case, there are three records created as shown in the deployment document

You will find the Public IP address of your instance in AWS here:

  • EC2 Service:

  • Instances:

Click on the instance id link, then Public IP address will be there:

2 - Connect to Instance

You can connect your instance with one of the methods on the screen:

I prefer ssh with keypair.pem:

Then, after successfully connecting:

I switched to the root user because I couldn’t find a way to make it run with the default ubuntu user. If anyone knows why or how to do it, please comment below.

3 - Deploy Traefik

Traefik handles many tasks in this app:

  1. Reverse Proxy/Load Balancer
  2. HTTPS/SSL Management
  3. Development Setup (not used for deployment, only for the local development)
  4. Production Configuration (docker-compose.traefik.yml)

And also it is easy to install it. It is almost the same as the original documentation with a couple of details that you may want to know, so follow the steps (italics are from the original documentation):

Traefik Public Network

This Traefik will expect a Docker “public network” named traefik-public to communicate with your stack(s).

This way, there will be a single public Traefik proxy that handles the communication (HTTP and HTTPS) with the outside world, and then behind that, you could have one or more stacks with different domains, even if they are on the same single server.

To create a Docker “public network” named traefik-public run the following command in your remote server:

docker network create traefik-public

Traefik Environment Variables

Again, the deployment document is correct, but if you close the terminal, all environment variables will be lost. By doing this instead, they will persist on the system. If you need to change them later, you will need to modify the .bashrc file at that time

The Traefik Docker Compose file expects some environment variables to be set in your terminal before starting it. You can do it by running the following commands in your remote server.

  • Create the username for HTTP Basic Auth, e.g.:
echo 'export USERNAME=admin' >> /root/.bashrc
  • Create an environment variable with the password for HTTP Basic Auth, e.g.:
echo 'export PASSWORD=changethis' >> /root/.bashrc
  • Use openssl to generate the “hashed” version of the password for HTTP Basic Auth and store it in an environment variable:
echo 'export HASHED_PASSWORD=$(openssl passwd -apr1 $PASSWORD)' >> /root/.bashrc

To verify that the hashed password is correct, you can print it:

echo $HASHED_PASSWORD
  • Create an environment variable with the domain name for your server, e.g.:
echo 'export DOMAIN=fastapi-project.example.com' >> /root/.bashrc
  • Create an environment variable with the email for Let’s Encrypt, e.g.:
echo 'export EMAIL=admin@example.com' >> /root/.bashrc

Note: you need to set a different email, an email @example.com won’t work.

Start the Traefik Docker Compose

Go to the directory where you copied the Traefik Docker Compose file in your remote server:

cd /root/code/traefik-public/

Now with the environment variables set and the docker-compose.traefik.yml in place, you can start the Traefik Docker Compose running the following command:

This part didn’t work for me on EC2 (missing repository kind of error)

docker compose -f docker-compose.traefik.yml up -d

So I only added --build at the end then it worked fine:

docker compose -f docker-compose.traefik.yml up -d --build

Important: If you do not use docker-compose.traefik.yml while building, it just won’t work. So be careful. At this point, you your Traefik network should be accessible. Try to log in to see if it works.

If it doesn’t then check my notes below.

4-Deploy the FastAPI Project

Preparation

Read this first before proceeding: OPTIONAL STEP: For a problem-free deployment on the cloud, you need to manage the database update history that is handled by Alembic in the full-stack-fastapi-template. As you build your app, it is possible that you may have made incremental changes to your local database via alembic without noticing (e.g., by changing models.py). Therefore, you may want to check your version history at beckend/app/alembic/versions and understand how it works. In my case, the version error caused by the addition of columns to non-existent tables which was working fine on the local server Easier solution: I dealt with this problem by deleting all the Alembic version history on my computer, deleting the database container (db), then recreating it from scratch in the local development environment after the development stage. Test it locally if it works fine, then put the final copy of the project on EC2, then deploy that final version.

to delete everything under beckend/app/alembic/versions :

- `alembic revision --autogenerate -m "all changes in one go."`
- `alembic upgrade head`

Environment Variables

You need to set some environment variables first.

Set the ENVIRONMENT, by default local (for development), but when deploying to a server you would put something like staging or production:

The only difference is writing all of them to the file.

echo 'export ENVIRONMENT=production' >> /root/.bashrc

Set the DOMAIN, by default localhost (for development), but when deploying you would use your own domain, for example:

echo 'export DOMAIN=fastapi-project.example.com' >> /root/.bashrc

For all the below I use the method like above

You can set several variables, like:

  • PROJECT_NAME: The name of the project, used in the API for the docs and emails.
  • STACK_NAME: The name of the stack used for Docker Compose labels and project name, this should be different for staging, production, etc. You could use the same domain replacing dots with dashes, e.g. fastapi-project-example-com and staging-fastapi-project-example-com.
  • BACKEND_CORS_ORIGINS: A list of allowed CORS origins separated by commas.
  • SECRET_KEY: The secret key for the FastAPI project, used to sign tokens.
  • FIRST_SUPERUSER: The email of the first superuser, this superuser will be the one that can create new users.
  • FIRST_SUPERUSER_PASSWORD: The password of the first superuser.
  • SMTP_HOST: The SMTP server host to send emails, this would come from your email provider (E.g. Mailgun, Sparkpost, Sendgrid, etc).
  • SMTP_USER: The SMTP server user to send emails.
  • SMTP_PASSWORD: The SMTP server password to send emails.
  • EMAILS_FROM_EMAIL: The email account to send emails from.
  • POSTGRES_SERVER: The hostname of the PostgreSQL server. You can leave the default of db, provided by the same Docker Compose. You normally wouldn’t need to change this unless you are using a third-party provider.
  • POSTGRES_PORT: The port of the PostgreSQL server. You can leave the default. You normally wouldn’t need to change this unless you are using a third-party provider.
  • POSTGRES_PASSWORD: The Postgres password.
  • POSTGRES_USER: The Postgres user, you can leave the default.
  • POSTGRES_DB: The database name to use for this application. You can leave the default of app.
  • SENTRY_DSN: The DSN for Sentry, if you are using it.

Deploy The Project

Ensure that the entire app is under /root/code/my-full-stack-app cd /root/code/my-full-stack-app Again, the only difference from the original documentation is adding --build at the end. docker compose -f docker-compose.yml up -d --build Important: It is crucial to explicitly use docker-compose.yml in the Docker Compose command. Otherwise, docker-compose.override.yml will kick in, and you won’t deploy your app properly

NOTES:

  • Learn how to clean the instance of previous failed attempts.
    • it is very annoying to see how your volumes can become full quickly.
    • For a clean start remove everything related to Traefik:
      • docker-compose -f docker-compose.traefik.yml down
      • docker network rm traefik-public
      • docker system prune -f
      • to check if there is anything left on the host environment
      • docker ps , docker network ls, docker volume ls
      • remove if there was anything left.
      • verify by running:
      • docker ps | grep traefik
    • if necessary do this for the app part too.