All docs

Python

Django, FastAPI and Flask, what Marina starts for each, and the binding mistake that hides an app.

Marina builds a Python app from a requirements.txt, pyproject.toml or Pipfile, or from a recognisable entry point at the root (main.py, app.py, server.py and a few others). pip, Poetry, PDM, uv and Pipenv all work; commit the lockfile.

marina vessels create api --repo acme/api

The start command

Marina recognises three frameworks and starts each the way it expects:

FrameworkWhat runs
Djangomanage.py migrate, then gunicorn against your WSGI app.
FastAPIuvicorn on main:app, bound to 0.0.0.0 and PORT.
Flaskgunicorn on main:app, bound to 0.0.0.0 and PORT.

Those defaults assume the conventional layout. If your ASGI app is not main:app, or you want workers or a different server, declare it in marina.toml:

[processes]
web = "uvicorn app.api:app --host 0.0.0.0 --port $PORT --workers 2"

Anything else starts the entry-point file directly.

The port

Marina sets PORT and routes to it. Bind that, on 0.0.0.0.

uvicorn.run(app, host="0.0.0.0", port=int(os.environ["PORT"]))

Binding 127.0.0.1 is the mistake that costs the most time here, because everything looks right: the build passes, the process starts, the logs are clean, and every request times out. 0.0.0.0 or nothing.

The Python version

From .python-version, .tool-versions, or runtime.txt. Without one you get a recent 3.x that will move under you eventually, so pin it.

3.12

Django, specifically

Two things are yours to handle.

Static files. Nothing serves your static/ directory for you. Run collectstatic as part of the build and serve them from the app; whitenoise is the usual answer and needs no extra service.

Migrations run at start. The default start command migrates and then serves, which is what you want on a first deploy and often not what you want later, when two versions are briefly serving at once. To take that back, declare a web without it and run migrations when you choose:

[processes]
web = "gunicorn myproject.wsgi:application --bind 0.0.0.0:$PORT"
marina run -- python manage.py migrate

ALLOWED_HOSTS has to include the address Marina gave you, and your own domain once you attach one.