Deploy FastAPI on VPS

Lemoncode21
5 min readApr 13, 2023

--

In this article, we will learn how to deploy a FastAPI web application on a VPS (Virtual Private Server) using Docker and PostgreSQL. We will start by creating a separate Docker Compose configuration for our database to prevent it from shutting down when we deploy the FastAPI application. Then we will create a controller for our note application and test it to make sure it is functioning properly. We will also create a Dockerfile and a Docker Compose configuration for our FastAPI application and deploy it on our VPS.

Article Content

  • Setup FastAPI service
  • Configuration FastAPI in docker
  • Integration FastAPI docker with Postgresql docker
  • Deploy to vps.

1. Setup FastAPI service

To start off we need to create a virtual environment and FastAPI.

# Create dir
mkdir app

cd app

#create virtual environment
python -m venv ./venv

#activate virtual environment (Windows)
.\venv\Scripts\activate

after That you can install fastapi

pip install fastapi

We will need an ASGI (Asynchronous Server Gateway Interface) server, in this case we will use Uvicorn.

pip install uvicorn

In the initial configuration we add the file main.py:

import uvicorn
from fastapi import FastAPI
from config import db


def init_app():
app = FastAPI()

@app.get("/")
def home():
return "Welcome Home"

@app.on_event("startup")
async def startup():
await db.create_all()

@app.on_event("shutdown")
async def shutdown():
await db.close()

from controller import router
app.include_router(router)

return app


app = init_app()

Let us initialize our database. we use postgresql as database. Before start dont forget install SQLAlchemy as Object Relation Mapping tool.

pip install SQLAlchemy psycopg2 sqlmodel

go to config.py and write the following:

from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlmodel import SQLModel

DB_CONFIG = f"postgresql+asyncpg://postgres:postgres@postgres-db-fastapi:5432/py_test_db"


class AsyncDatabaseSession:

def __init__(self):
self.session = None
self.engine = None

def __getattr__(self, name):
return getattr(self.session, name)

def init(self):
self.engine = create_async_engine(DB_CONFIG, future=True, echo=True, pool_size=10, max_overflow=20)
self.session = sessionmaker(self.engine, expire_on_commit=False, class_=AsyncSession)()

async def create_all(self):
self.init()
async with self.engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)


db = AsyncDatabaseSession()


async def commit_rollback():
try:
await db.commit()
except Exception:
await db.rollback()
raise

Let’s head over to models.py. We are going to define out models here.

from datetime import date, datetime
from typing import Optional

from sqlalchemy import Enum, Column, DateTime
from sqlmodel import SQLModel, Field


class Note(SQLModel, table=True):
__tablename__ = "note"

id: Optional[int] = Field(None, primary_key=True, nullable=False)
name: str
description: str

create_at: datetime = Field(default_factory=datetime.now)
modified_at: datetime = Field(
sa_column=Column(DateTime, default=datetime.now,
onupdate=datetime.now, nullable=False)
)

create file schema.py with the Pydantic models

from typing import Optional, TypeVar
from pydantic import BaseModel

T = TypeVar('T')


class NoteCreate(BaseModel):
name: str
description: str


class ResponseSchema(BaseModel):
detail: str
result: Optional[T] = None

now create controller.py

from fastapi import APIRouter
from repository import NoteRepository

from schema import NoteCreate, ResponseSchema

router = APIRouter(
prefix="/note",
tags=['note']
)


@router.post("", response_model_exclude_none=True)
async def create_note(create_form: NoteCreate):
await NoteRepository.create(create_form)
return ResponseSchema(detail="Successfully created data !")


@router.get("", response_model_exclude_none=True)
async def get_all_note():
data = await NoteRepository.get_all()
return ResponseSchema(detail="Successfully fetch data !", result=data)

let’s generating dependecies to create requirements.txt

pip freeze > requirement.txt

With the main application file created, start the application by running the following command in the terminal:

uvicorn main:app --reload

With the application running, open a web browser and navigate to http://localhost:8000.

2. Configuration FastAPI in docker

The next step is to create a Dockerfile that will define how the Docker container is built. Create a file named Dockerfile in the same directory as the main.py file and add the following code:

FROM python:3.9

# create app directory
WORKDIR /usr/src/app

# set work directory
WORKDIR /app

# set env variables
ENV PYTHONDONTWRITTERBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt

# copy project
COPY . .

Next, let’s create a Docker Compose file to define our app and its dependencies. Create a new file called docker-compose.yml in the same directory as the Dockerfile, and add the following code:

version: '3'

services:
app-fastapi:
build: .
command: uvicorn main:app --host 0.0.0.0
ports:
- "8888:8000"
networks:
- app-fastapi


networks:
app-fastapi:
driver: bridge

To run this docker just use command

docker-compose up -d

After you run fastapi docker you will be found error like this:

This error is due to the connection to the database not found. Next we have to create docker compose for database. Create folder /database-fastapi/docker-compose.yml

version: '3.3'

services:
postgres-db-fastapi:
image: postgres
restart: always
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=postgres
volumes:
- ./data:/var/lib/postgresql/data/
ports:
- "5432:5432"
networks:
- postgres-database

pgadmin:
image: dpage/pgadmin4
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: admin@gmail.com
PGADMIN_DEFAULT_PASSWORD: password
ports:
- "7777:80"
depends_on:
- postgres-db-fastapi
networks:
- postgres-database

networks:
postgres-database:
driver: bridge

And run postgresql docker compose just type

docker-compose up -d

3. Integration FastAPI docker with Postgresql docker

Now is the time to do docker fastapi integration with docker postgresql. you just need to add the network to the compose fastapi docker. but before that you have to see the list of networks used by typing the command as follows

docker network list

after knowing the network list, we change the fastapi docker compose to the following

version: '3'

services:
app-fastapi:
build: .
command: uvicorn main:app --host 0.0.0.0
ports:
- "8888:8000"
networks:
- app-fastapi
- database-fastapi_postgres-database

networks:
app-fastapi:
driver: bridge

database-fastapi_postgres-database:
external: true

then run the fastapi docker again.

4. Deploy to vps

After fastapi docker is running and postgresql docker is running locally it’s time for us to upload it to our vps. Upload folder /database-fastapi/docker-compose.yml To transfer a file from the local machine to the remote machine, use the following command:

scp /path/to/local/file remote_user@remote_host:/path/to/remote/destination

Replace /path/to/local/file with the path to the local file you want to transfer, remote_user with the username of the remote machine, remote_host with the hostname or IP address of the remote machine, and /path/to/remote/destination with the path to the destination directory on the remote machine.

for the fastapi code I use git pull on the server for every change. after all the folders are saved in vps run with docker-compose up -d.

And those are the stages in deploying fastapi on a VPS. For more details, you can see this video on YouTube.

--

--

Lemoncode21
Lemoncode21

No responses yet