Host Flask Application: A Production Deployment Plan

Glowing server racks connect to a secure cloud with indigo data streams.

A Flask app can work perfectly on your laptop and still fail the first real user request. The usual causes are simple: the app runs with Flask’s development server, secrets sit in the repository, or the host can’t reach the correct port.

To host Flask application code in production, you need a production web server, a clear configuration method, and a recovery process. Start with a small deployment that you can test, monitor, and roll back.

PREPARE THE APP BEFORE YOU DEPLOY

Production hosting starts in the repository, not in a hosting dashboard. Your code needs a stable entry point, pinned dependencies, environment-based configuration, and a basic health route.

Create a production entry point

Do not point a host at the file that starts app.run(). That command starts Flask’s development server. It is useful for local work. It is not a production server.

Flask’s own documentation warns that the built-in server is not designed to be secure, stable, or efficient in production. Use a WSGI server such as Gunicorn instead.

A clean project can use this structure:

  • src/myapp/__init__.py contains your create_app() function.
  • wsgi.py creates the app object that Gunicorn imports.
  • requirements.txt or a lock file stores tested dependencies.
  • .env.example lists required variable names without values.

Your wsgi.py file can be one line:

from myapp import create_app; app = create_app()

Gunicorn then starts the app with:

gunicorn wsgi:app

The pattern is simple. wsgi is the file name. app is the Flask object inside that file.

Separate configuration from code

Keep environment-specific values out of Python files. Your database URL, Flask secret key, API tokens, email credentials, and payment keys belong in the host’s secret settings.

Load required values at startup. Stop the app if a required value is missing. A broken configuration should fail during deployment, not after customers begin using the service.

Use a short list of variables:

  • FLASK_SECRET_KEY for signed sessions and CSRF protection.
  • DATABASE_URL for your production database connection.
  • APP_ENV=production for environment-aware settings.
  • PORT when a managed host provides the listening port.
  • Vendor keys such as STRIPE_SECRET_KEY or SENDGRID_API_KEY.

Do not commit .env files. Commit .env.example instead, with blank values and setup notes.

A deployment that starts with missing configuration is not healthy. A process that fails fast gives you a clear fix before traffic reaches it.

WHERE TO HOST FLASK APPLICATION CODE

Your hosting choice controls how much infrastructure work your team owns. Managed platforms remove server administration. A VPS gives you more control. Containers give you portability, but they do not remove operational work.

Hosting optionGood fitYou manageCommon deployment path
Render or RailwaySmall apps and first production releasesApp settings, logs, database setupConnect GitHub and set build and start commands
Google Cloud RunRequest-based services and container workloadsCloud project, permissions, service settingsDeploy source or a container image
VPS with NginxPredictable workloads and custom server needsLinux, patches, TLS, firewall, backupsSSH, systemd, Gunicorn, Nginx
Docker on any hostTeams that need matching local and production environmentsImage builds, registry, host runtimeBuild image, push image, run service

Managed platforms reduce setup time

Render and Railway are practical when you want a public app without maintaining Ubuntu updates, Nginx packages, and systemd units. You connect a Git repository, add environment variables, and set the startup command.

Render’s Flask deployment guide uses a Python build command and a Gunicorn start command. Railway also provides a Flask deployment workflow with GitHub, CLI, and Docker options.

Use a managed platform when your team needs to ship a working internal tool, API, or customer portal quickly. Read the platform’s current database, sleep behavior, region, custom domain, and log retention settings before launch.

Cloud Run fits container-based services

Cloud Run is a good match for apps that already use Docker or need Google Cloud services. It runs containers and routes HTTP traffic to the port your app exposes.

For a source-based release, Google documents this command:

gcloud run deploy --source .

Its Python Flask quickstart walks through the required prompts for service name, region, and public access.

Cloud Run can scale request capacity up and down. Set a maximum instance count before launch. An uncapped service can create more database connections and more cost during a traffic spike.

RUN GUNICORN INSTEAD OF FLASK’S DEVELOPMENT SERVER

Gunicorn is the process that receives web requests and passes them to Flask. It replaces flask run and app.run() in production.

For a simple app, use:

gunicorn --bind 0.0.0.0:8000 wsgi:app

For a managed host that sets PORT, use a shell command such as:

gunicorn --bind 0.0.0.0:$PORT wsgi:app

Your platform may provide the start command through its dashboard, a Procfile, a Docker CMD, or a systemd service.

Start with modest worker settings

More workers are not automatically better. Each worker consumes memory and may open database connections. Start with two workers for a small service, then review memory use, request latency, and database limits.

A practical command is:

gunicorn --workers 2 --threads 4 --timeout 60 --bind 0.0.0.0:$PORT wsgi:app

Use threads only when your app spends time waiting on HTTP calls or database responses. CPU-heavy work such as image processing, report generation, or machine learning should move to a worker queue. A web request should return before a platform timeout.

Put a reverse proxy in front of a VPS app

On a VPS, Gunicorn should usually listen on 127.0.0.1:8000. Nginx listens publicly on ports 80 and 443, terminates TLS, serves static files, and forwards app requests to Gunicorn.

Your systemd service can run:

/srv/myapp/.venv/bin/gunicorn --workers 2 --bind 127.0.0.1:8000 wsgi:app

Nginx should forward the original host and protocol headers. Flask needs them for correct redirects, secure cookies, and URL generation. Use ProxyFix only when your proxy configuration is known and controlled.

PACKAGE THE APP WITH DOCKER WHEN PORTABILITY MATTERS

Docker gives your laptop, staging server, and production host the same Python version and dependency installation process. It is useful when several developers deploy the app or when you may change providers later.

A basic Dockerfile needs these steps:

  1. Start with a tested Python slim image, such as FROM python:3.13-slim.
  2. Set the work directory with WORKDIR /app.
  3. Copy dependency files before application files.
  4. Install dependencies with pip install --no-cache-dir -r requirements.txt.
  5. Copy the application source.
  6. Start Gunicorn with CMD ["gunicorn", "--bind", "0.0.0.0:8000", "wsgi:app"].

Set PYTHONUNBUFFERED=1 so application logs reach the host without delay.

Keep images small and predictable

Add a .dockerignore file. Exclude .git, virtual environments, local databases, test artifacts, .env files, and Python cache folders. A smaller image builds faster and reduces accidental file exposure.

Build and test locally:

docker build -t flask-service:local .

docker run --rm -p 8000:8000 --env-file .env flask-service:local

Then check the service:

curl http://localhost:8000/healthz

Docker supports a HEALTHCHECK instruction, and the Dockerfile reference documents its available settings. Health checks help a host identify a process that still exists but no longer returns useful responses.

PROTECT SECRETS, DEPENDENCIES, AND PUBLIC ROUTES

A public Flask endpoint is part of your attack surface. Treat secrets, package upgrades, request limits, and admin access as operating controls.

Pin what you test

Do not deploy a vague dependency range and hope the next build matches the last one. Pin direct dependencies in requirements.txt, then use a lock process that records transitive versions.

Your requirements file should contain tested versions such as Flask==<tested-version> and gunicorn==<tested-version>. Rebuild the environment in CI or staging before promoting changes.

Review dependency updates on a schedule. Apply security updates after testing them against your app. Keep the previous working image or release available for rollback.

Store secrets in approved host controls

Use the secret manager or encrypted environment variable settings provided by your host. Do not place credentials in Git commits, Docker images, shell history, support tickets, or error logs.

Rotate a key if it appears in a repository or deployment log. Delete the exposed value first, then issue a replacement. Removing it from the latest commit does not remove it from Git history.

Restrict production access. Give each team member their own account. Use MFA. Remove access when a contractor or employee no longer supports the application.

ADD HEALTH CHECKS, LOGS, AND A ROLLBACK PROCESS

A green deployment status does not prove that the app works. The process may be running while the database connection fails or a required environment variable is wrong.

Use separate liveness and readiness routes

Create a fast /healthz route that returns HTTP 200 without calling external services. This tells the host that the Flask process can answer a request.

Create /readyz for deeper checks. It can confirm database connectivity, migrations, or a required cache connection. Keep it lightweight. Do not expose passwords, configuration values, or detailed stack traces in either response.

Test both after every release:

curl -i https://your-domain.com/healthz

curl -i https://your-domain.com/readyz

Log request errors to standard output and standard error. Managed hosts collect these logs automatically. On a VPS, configure systemd and your log service to retain them.

Define who owns a failed release

Write a small deployment record before you need it. Name the release owner, the location of the release log, and the method used to identify the last trusted version.

Track these fields:

  • Commit SHA or container image digest.
  • Deployment date, host, and environment.
  • Database migration version.
  • Health-check result and error count after release.
  • Person responsible for approval or rollback.
  • Last known working release.

Use bounded retries with backoff for temporary network errors. Do not retry missing secrets, invalid credentials, or schema failures forever. Those errors need a human fix.

Check logs and health routes after deployment. Review error rates, response times, disk use, and database connections each week. Test backups and restore procedures before an outage forces the issue.

FINAL DEPLOYMENT CHECK

To host Flask application code safely, keep the production path boring. Run Gunicorn, bind to the correct host port, inject secrets at runtime, and test a health route after each release.

The last trusted release is your safety net. Record it, keep it deployable, and roll back when a new version fails its checks.

Leave a Reply

Your email address will not be published. Required fields are marked *

Verified by MonsterInsights