1. Why Not Use flask run
Flask ships with a development server. You start it with flask run or app.run(). It is perfect on your laptop and wrong for real visitors.
The development server has no worker restarts, no request timeouts and limited protection against slow clients. Flask itself prints a warning that tells you not to use it in production. Listen to that warning.
A production Flask app needs three parts. You need Flask for your code, Gunicorn to run it, and a reverse proxy such as Caddy or Nginx to handle https.
2. The Three Parts of a Production Setup
Each part does one job. A request passes through them in this order.
- Caddy, the reverse proxy. Caddy accepts visitors on ports 80 and 443. It handles the https certificate and passes each request to Gunicorn.
- Gunicorn, the WSGI server. Gunicorn runs several copies of your app, called workers. It hands each request to a free worker and restarts workers that crash or hang.
- Flask, your app. Flask runs your route, queries the database, renders the template and returns the response.
WSGI is the standard that lets Gunicorn and Flask talk to each other. You never write WSGI code yourself. You only point Gunicorn at your Flask object.
3. Gunicorn Settings That Matter
Gunicorn's documentation suggests this starting point for the number of workers.
workers = (2 x CPU cores) + 1
Memory often limits you before the processor does. On a plan with one processor core and 512 MB of memory, start with two workers. Add a third if memory allows. This command covers the settings most apps need.
gunicorn --bind 0.0.0.0:$PORT \
--workers 2 \
--timeout 30 \
--access-logfile - \
--error-logfile - \
app:app
The --timeout 30 setting restarts a worker that hangs for 30 seconds. The two dashes send logs to the screen instead of a file, so your host can collect and show them.
4. Keep Secrets Out of Your Code
Never write passwords or API keys into your source code. Code gets shared, copied and committed to GitHub. Read secrets from environment variables instead.
import os
from flask import Flask
app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
app.config["DEBUG"] = os.environ.get("FLASK_DEBUG", "false").lower() == "true"
Reading SECRET_KEY with square brackets makes the app stop at startup if the secret is missing. That beats running with a weak fallback key that nobody notices.
On Cloudilio, you add secrets on your app's Secrets page. Cloudilio stores them encrypted and gives them to your app as environment variables when it starts. The secrets guide explains the details.
5. Doing It Yourself vs. Cloudilio
On your own Linux server, this setup takes an afternoon. You install Python, create a systemd service for Gunicorn, write a Caddy or Nginx config and set up certificates. Then you keep all of it patched.
Cloudilio does those steps for you. Upload a project with an app.py and a requirements.txt, and Cloudilio does the following.
- It installs your dependencies into a separate container for your app.
- It starts Gunicorn on the right port.
- It gives your app a data folder at
/app/datathat survives every update. - It connects your domain and turns on https automatically.
6. Add a Health Check
A health check is a small page that says your app is working. Hosts and monitoring tools visit it to decide whether your app is up. Add one in four lines.
@app.route("/health")
def health():
return {"status": "ok"}, 200
Cloudilio visits this page before it switches visitors to a new version. If the check fails, your old version stays live. Cloudilio also checks your app around the clock and emails you if it stops answering. Your dashboard shows memory use, uptime and live logs.
Cloudilio Engineering Team
Application Delivery Group • CloudilioCloudilio hosts Python apps. You publish Flask, Django, FastAPI, Streamlit and plain Python scripts without managing a server, and your app's data stays safe between updates.