Deploy FastAPI on VPS
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.