Logo

Deploy a FastAPI (or Node) API with PostgreSQL on a VPS

By Elliot, BearHost·

This is the setup we recommend for a mobile or web app backend: one Ubuntu server running the API as a systemd service behind Nginx, with PostgreSQL on the same box. It fits comfortably on a VPS Cub for development and a VPS Bear for a first production launch.

Replace api.example.com with your domain, pointed at the server's IP (how to point a domain).

1. First login and basics

ssh root@YOUR_SERVER_IP
apt update && apt upgrade -y
adduser deploy && usermod -aG sudo deploy
ufw allow OpenSSH && ufw allow 80 && ufw allow 443 && ufw enable

2. PostgreSQL

apt install -y postgresql
sudo -u postgres psql -c "CREATE USER app WITH PASSWORD 'choose-a-long-password';"
sudo -u postgres psql -c "CREATE DATABASE app OWNER app;"

PostgreSQL listens only on localhost by default, which is what you want: the API talks to it locally and nothing from the internet can reach it.

3. The application

FastAPI example (Node is the same shape with npm ci and node server.js):

apt install -y python3-venv git
su - deploy
git clone https://github.com/you/your-api.git ~/api
cd ~/api && python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt uvicorn

Put secrets in ~/api/.env (database URL postgresql://app:password@localhost/app, API keys) and never in Git.

4. Run it as a service

Create /etc/systemd/system/api.service:

[Unit]
Description=API
After=network.target postgresql.service

[Service]
User=deploy
WorkingDirectory=/home/deploy/api
EnvironmentFile=/home/deploy/api/.env
ExecStart=/home/deploy/api/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --workers 2
Restart=always

[Install]
WantedBy=multi-user.target

Then systemctl enable --now api. The API now starts on boot and restarts if it crashes. Logs: journalctl -u api -f.

5. Nginx and HTTPS

apt install -y nginx certbot python3-certbot-nginx

/etc/nginx/sites-available/api:

server {
    server_name api.example.com;
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
ln -s /etc/nginx/sites-available/api /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx
certbot --nginx -d api.example.com

Certbot issues a free certificate and renews it automatically. Your API is now served at https://api.example.com.

6. Deploying updates

cd ~/api && git pull && . .venv/bin/activate && pip install -r requirements.txt && sudo systemctl restart api

Wrap that in a script or a GitHub Action over SSH when it gets tedious.

7. Backups

Enable the Automated Backups add-on for a daily copy of the whole server, and additionally dump the database nightly so you can restore just the data:

echo '0 3 * * * pg_dump -U app app | gzip > /home/deploy/backups/app-$(date +\%F).sql.gz' | crontab -u deploy -

Scaling when users arrive

  1. Upgrade the plan (Upgrade tab, pay the difference, stop/start to apply). Add --workers equal to the vCPU count.
  2. Move PostgreSQL to its own VPS when the database becomes the bottleneck; change the database URL in .env.
  3. Add a second API server behind a load balancer or DNS round-robin once a single server is saturated.

Most apps never need step 3. A VPS Grizzly handles thousands of daily active users for a typical mobile backend.

Tags:#fastapi#python#node#postgresql#nginx#deployment#ubuntu