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 -yOption 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 gunicornCreate 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:appOption 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 --noinputUpdate 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:applicationOption 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 uvicornCreate 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 4Create 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.targetFor Django, change ExecStart to:
ExecStart=/var/www/myapp/venv/bin/gunicorn --workers 4 --bind 127.0.0.1:8000 myproject.wsgi:applicationFor FastAPI, change ExecStart to:
ExecStart=/var/www/myapp/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --workers 4Enable and start the service:
chown -R www-data:www-data /var/www/myapp
systemctl daemon-reload
systemctl enable myapp
systemctl start myappConfigure 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 nginxAdd 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-runYour app is now served over HTTPS with automatic certificate renewal.
Why Bare Metal Beats Heroku for Python
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 -fto tail logs - Updates: Run
apt update && apt upgradeweekly - Backups: Cron job for database dumps to object storage or another server
- Monitoring: Use
htopfor 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 deployServers from $6/mo. 3 seconds to deploy.