← Back to Blog
PythonTutorial

Hosting Python Apps on Bare Metal: Flask, Django, FastAPI

Heroku charges $25/mo for a single dyno with 512 MB RAM. Railway and Render charge per-usage with unpredictable bills. A bare metal server gives you 4 GB RAM, dedicated CPU, and full root access for $6/mo. Here is how to deploy Flask, Django, and FastAPI on bare metal in under 10 minutes.

Prerequisites

Deploy a server on RAW and install dependencies:

npx rawhq deploy
ssh root@your-server-ip
apt update && apt upgrade -y
apt install python3 python3-pip python3-venv nginx certbot python3-certbot-nginx -y

Option 1: Deploy Flask

Flask is the simplest starting point. Ideal for APIs, microservices, and lightweight web apps.

Set Up the Application

mkdir -p /var/www/myapp && cd /var/www/myapp
python3 -m venv venv && source venv/bin/activate
pip install flask gunicorn

Create your Flask app at /var/www/myapp/app.py:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "Hello from Flask on bare metal"

@app.route("/health")
def health():
    return "ok"

Run with Gunicorn

gunicorn --workers 4 --bind 127.0.0.1:8000 app:app

Option 2: Deploy Django

Django is the full-featured framework. Best for applications that need an ORM, admin panel, authentication, and templating out of the box.

Set Up the Application

mkdir -p /var/www/myapp && cd /var/www/myapp
python3 -m venv venv && source venv/bin/activate
pip install django gunicorn
django-admin startproject myproject .
python manage.py migrate
python manage.py collectstatic --noinput

Update ALLOWED_HOSTS in myproject/settings.py:

ALLOWED_HOSTS = ["yourdomain.com", "your-server-ip"]
STATIC_ROOT = "/var/www/myapp/static/"

Run with Gunicorn

gunicorn --workers 4 --bind 127.0.0.1:8000 myproject.wsgi:application

Option 3: Deploy FastAPI

FastAPI is the modern choice for high-performance async APIs. It uses Uvicorn (ASGI) instead of Gunicorn (WSGI) and generates OpenAPI docs automatically.

Set Up the Application

mkdir -p /var/www/myapp && cd /var/www/myapp
python3 -m venv venv && source venv/bin/activate
pip install fastapi uvicorn

Create your FastAPI app at /var/www/myapp/main.py:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def index():
    return {"message": "Hello from FastAPI on bare metal"}

@app.get("/health")
async def health():
    return {"status": "ok"}

Run with Uvicorn

uvicorn main:app --host 127.0.0.1 --port 8000 --workers 4

Create a Systemd Service

Systemd keeps your app running after reboots and restarts it on crashes. This works for all three frameworks.

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

[Unit]
Description=My Python App
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/var/www/myapp/venv/bin/gunicorn --workers 4 --bind 127.0.0.1:8000 app:app
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

For Django, change ExecStart to:

ExecStart=/var/www/myapp/venv/bin/gunicorn --workers 4 --bind 127.0.0.1:8000 myproject.wsgi:application

For FastAPI, change ExecStart to:

ExecStart=/var/www/myapp/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --workers 4

Enable and start the service:

chown -R www-data:www-data /var/www/myapp
systemctl daemon-reload
systemctl enable myapp
systemctl start myapp

Configure Nginx as Reverse Proxy

Nginx handles incoming traffic, serves static files, and proxies requests to your Python application.

Create /etc/nginx/sites-available/myapp:

server {
    listen 80;
    server_name yourdomain.com;

    location /static/ {
        alias /var/www/myapp/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable the site and reload Nginx:

ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx

Add SSL with Certbot

Free HTTPS in one command. Certbot modifies your Nginx config automatically and sets up auto-renewal.

certbot --nginx -d yourdomain.com

# Verify auto-renewal
certbot renew --dry-run

Your app is now served over HTTPS with automatic certificate renewal.

Why Bare Metal Beats Heroku for Python

FeatureHerokuRAW
Pricing$25/mo (1 dyno)$6/mo
RAM512 MB4 GB
StorageEphemeral40 GB NVMe
Database+$9/mo (Postgres)$0 (same server)
SSLIncludedFree (Certbot)
Cold startsYes (Eco dynos)Never
Root accessNoFull SSH
Workers1 per dynoAs many as CPU allows
Bandwidth2 TB/moUnlimited

For a typical Python web app with a database, Heroku costs $34+/mo (1 dyno + Postgres). RAW costs $6/mo with 8x the RAM, persistent storage, and no cold starts. That is a 82% savings.

Production Checklist

  • Firewall: Allow only ports 22, 80, and 443 with ufw
  • Process manager: Systemd keeps your app running and restarts on crash
  • Logging: Use journalctl -u myapp -f to tail logs
  • Updates: Run apt update && apt upgrade weekly
  • Backups: Cron job for database dumps to object storage or another server
  • Monitoring: Use htop for quick checks or set up a lightweight monitoring agent

The Bottom Line

Hosting Python apps on bare metal is straightforward: install Python, run Gunicorn or Uvicorn behind Nginx, add a systemd service, and get SSL with Certbot. The whole setup takes under 10 minutes and costs $6/mo for dedicated hardware that outperforms a $25/mo Heroku dyno.

Whether you are running Flask, Django, or FastAPI, bare metal gives you full control, predictable costs, and the performance your application deserves.

Try RAW Free

npx rawhq deploy

Servers from $6/mo. 3 seconds to deploy.