1. The app, and what to install
Keep the app in app.py as usual:
# app.py
import os
from dotenv import load_dotenv
from fastapi import FastAPI
load_dotenv()
app = FastAPI()
@app.get("/")
def home():
return {"ok": True}# requirements.txt
fastapi
uvicorn[standard]
python-dotenvuvicorn[standard] adds a faster event loop and HTTP parser, and WebSocket support. Plain uvicorn also works, more slowly and without WebSockets.
2. Start it the way a server should
--reload watches your files and restarts on every change. On a server it costs memory, and it runs a single process. Start uvicorn from a small main.py instead, which is the file that gets run:
# main.py: starts app.py under uvicorn
import os
import uvicorn
if __name__ == "__main__":
uvicorn.run(
"app:app",
host="0.0.0.0",
port=int(os.environ.get("SERVER_PORT") or os.environ.get("PORT") or 8000),
workers=2,
proxy_headers=True,
forwarded_allow_ips="*",
)host="0.0.0.0": listen on every interface. An app on127.0.0.1starts fine and nobody can reach it.port: the one the host gives you, inSERVER_PORTon SnowServers and oftenPORTelsewhere.workers=2: two processes, so one slow request does not hold up the rest. Each is a full copy of the app in memory; two suit a 1 GB plan. With workers, the app has to be given as the string"app:app", not the object.proxy_headersandforwarded_allow_ips: next section.
3. Behind HTTPS: the proxy headers
When HTTPS is handled in front of your app, uvicorn receives plain HTTP from the proxy. The proxy puts the real scheme and the visitor's address in X-Forwarded-Proto and X-Forwarded-For. uvicorn only believes those headers from the addresses in forwarded_allow_ips, which is 127.0.0.1 by default. When the proxy connects from another address, as it does from outside a container, nothing is believed: request.url says http://, url_for builds http:// links, and every visitor seems to come from the proxy.
forwarded_allow_ips="*" trusts the headers from anyone. That is right when every request comes through a proxy that sets them itself, and it is how the example above is meant for SnowServers, where the proxy overwrites both. Your server's own address and port also answer plain HTTP to anyone who has them, and a request sent there directly can carry made-up headers, so do not use the visitor address from X-Forwarded-For for anything that matters, such as allow-lists.
4. Secrets and settings
API keys and database passwords go in a .env file next to the app, never in the code or in Git; load_dotenv() in app.py reads it, and the values are in os.environ. Add .env to .gitignore.
FastAPI publishes interactive API documentation at /docs and /redoc. That is handy, and on a public site it is a map of every endpoint. If you do not want that, turn them off:
app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)5. Test it the way the server will run it
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
SERVER_PORT=8000 python main.pyOn Windows, set the port first (set SERVER_PORT=8000 in cmd, $env:SERVER_PORT=8000 in PowerShell). If http://localhost:8000 answers, the host will too.
Hosting it on SnowServers
- Choose a plan on the website plans page and pick A website and Python at checkout. Flurry, at 1 GB, runs most FastAPI apps.
- Upload
app.py,main.pyandrequirements.txtin Files, or paste your GitHub repository into Git repository on the Startup tab. Put.envin by hand. main.pyis picked as the start file on its own, ahead of the starterapp.py. Your ownapp.pyreplaces the starter's.- Restart from the console. It shows the packages installing and uvicorn starting its workers.
- On the Website tab, add your domain and the DNS records it shows. HTTPS follows on its own, usually within a few minutes.
WebSockets work through your domain. Uploads can be up to 25 MB each, and a request that sends nothing back for two minutes is cut off by the proxy, so long jobs belong in a background task that the client checks on. The website docs cover the rest.
Common problems
- "Error loading ASGI app. Could not import module": the string in
uvicorn.rundoes not match your files."app:app"means the variableappinapp.py; forsrc/api.pyit would be"src.api:app". - "You must pass the application as an import string":
workerswas used with the app object. Pass the string. - "This site is not answering": uvicorn is on
127.0.0.1or the wrong port. Use0.0.0.0andSERVER_PORT. - Links and redirects go to
http://:forwarded_allow_ipsdoes not include the proxy. - Out of memory: fewer workers, or a bigger plan. The Crashes tab shows memory over the last day.
Did this page help?
Thanks for the feedback.