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 deploy7-day free trial. 13 seconds to deploy. No credit card.