← 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/mo20 TB/mo

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

7-day free trial. 13 seconds to deploy. No credit card.