Flask needs one thing before it deploys: a gunicorn start command. Add ProxyFix so URLs and cookies know they're on HTTPS, then push.
Last updated
These are the defaults. Override any of them in Settings → Build.
flask gunicorn
Pick the start command that matches your layout:
# app.py with app = Flask(__name__) gunicorn app:app # application factory in myapp/__init__.py gunicorn "myapp:create_app()" # two workers (Pro and above) gunicorn -w 2 app:app
Coming from Heroku? A Procfile with a web: line is used as the start command, so you can leave it in the repo. Pin Python with a .python-version file; runtime.txt is not read.
HTTPS ends at the platform's proxy. Tell Flask to trust the forwarded headers so url_for(..., _external=True) builds https:// links and redirects stay on HTTPS:
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
app.config.update(
SECRET_KEY=os.environ["SECRET_KEY"],
SESSION_COOKIE_SECURE=True,
)Set SECRET_KEYin the project's env vars. Without a fixed key, sessions and flashed messages break on every deploy.
Every deploy requests your health check path (/ by default) and only goes live once it returns a 2xx or 3xx status. The platform keeps checking it after that. If / returns 404 or needs a login, add a route like /healthz that returns 200 and set it as the Health Check Path in Settings. Otherwise the deploy fails and the previous version keeps serving.
@app.get("/healthz")
def healthz():
return "ok"Add a Postgres addon and DATABASE_URL is injected automatically. Flask-SQLAlchemy uses SQLAlchemy 2.x, which rejects the postgres:// scheme the URL comes with:
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"].replace(
"postgres://", "postgresql+psycopg://", 1
)Add psycopg[binary] to your requirements. With Flask-Migrate, run migrations at start:
flask --app app db upgrade && gunicorn app:app
<project-slug>.ethiodeploy.com. Every push to the default branch redeploys, and the old version keeps serving until the new one is healthy.Build fails: No start command could be found
Cause: Flask has no default start command.
Fix: Set the Start command to gunicorn app:app, or add a Procfile web: line.
Failed to find attribute 'app' in 'app'
Cause: The gunicorn target doesn't match your module or variable name.
Fix: Use module:variable, or "module:create_app()" for a factory.
Logins don't stick or flashed messages vanish after a deploy
Cause: SECRET_KEY changes on each start.
Fix: Set a fixed SECRET_KEY env var.
Can't load plugin: sqlalchemy.dialects:postgres
Cause: SQLAlchemy 2.x doesn't accept the postgres:// scheme.
Fix: Rewrite it to postgresql+psycopg:// as shown above.
Redirects or external URLs use http://
Cause: Flask doesn't know the original request was HTTPS.
Fix: Wrap the app in ProxyFix.
Still stuck? Check the deployment's build log in the dashboard, or see Troubleshooting.
Can I host a Flask app for free?
Yes. The Free plan runs one project with a 512 MB Postgres database at no cost. It sleeps after 30 minutes without traffic, and the container has 256 MB of memory, enough for a small app with one or two gunicorn workers.
Do I still need a Procfile?
No. Set the start command in Settings → Build. If your repo already has a Procfile, its web line is used and you can skip the setting.
Can I run flask run in production?
Don't. flask run starts Werkzeug's development server, which isn't built for production traffic. Use gunicorn.
Ready to deploy? Sign in with GitHub and pick your repo. No Dockerfile, no YAML.
See the full docs for custom domains, env vars, addons, and billing.