A Flask application under Gunicorn. FastAPI, Starlette and any other ASGI
application are the same with another start, below. Django has a folder of
its own: ../django/.
Handbook: Python · Python image · vallic.yaml
| File | What it does |
|---|---|
vallic.yaml |
The manifest: Python and PostgreSQL, the uv build with .venv kept between builds, Gunicorn as start, Flask-Migrate on deploy, one cron job, a health check |
pyproject.toml, uv.lock |
Flask, Flask-SQLAlchemy and Flask-Migrate, psycopg 3, Gunicorn |
app.py |
The secret key from VALLIC_ENTROPY, the routed hostnames trusted, the proxy believed, a health check that asks the database, and a sanitize command |
.vallic/commands/sanitization.yml |
Runs flask sanitize when production's data is copied into staging or development |
migrations/ is what flask --app app db init and flask --app app db migrate write; commit it, and the deploy step applies it. jobs/digest.py,
named in vallic.yaml, stands for your own scheduled job.
Commit uv.lock with your own project — run uv lock after changing
dependencies. The build installs exactly what it says (--locked), and fails
rather than resolving anything new.
Under Gunicorn with Uvicorn's worker, which keeps Gunicorn's process
management — add uvicorn-worker to the dependencies:
start: python -m gunicorn main:app -k uvicorn_worker.UvicornWorkerOr under Uvicorn alone. It already listens on every address
(UVICORN_HOST); the port is yours to pass:
start: python -m uvicorn main:app --port $PORTuv is in the image, and so is pip. Create the environment in the build and
install into it; Poetry is not in the image, but poetry export -f requirements.txt gives pip a file to read.
build:
cache:
- .venv
steps:
- python -m venv .venv
- .venv/bin/pip install -r requirements.txt- Run everything as
python -m …— the server, deploy steps, cron, workers. The release is the build's checkout moved, and onlypythonfollows it;.venv/bin/gunicornstill names the path it was built in. DATABASE_URLis used as it is. It startspostgresql://, which SQLAlchemy 2.1 and psycopg take without a change. On MariaDB it startsmysql://, which SQLAlchemy reads asmysqlclient— the image has the headers to build it.- The version is
runtime.python, whatever a.python-versionfile asks for. - The release is read-only. Write what you keep under
VALLIC_PRIVATE_DIR(private/at the root of the release), or declare a directory undermounts. Bytecode is compiled in the build; at runtime Python does not try. - More than one web server: a session or a cache in a process's memory is
not there on the next request. Add
- valkey: '9.0'underservicesand keep them atREDIS_HOSTandREDIS_PORT. - Celery, RQ and Dramatiq are
workers: the application's own image and release, supervised, with no port. See../django/for one. - Mail goes to
SMTP_HOSTon port 25, with no login and no TLS — the relay is inside the stack. - Nothing is scheduled for you, and a restore clears no cache.
Written by the platform into every container of this environment — read them, never commit their values. The full list, with what each one is for: Variables.
| Variable | What it is |
|---|---|
VALLIC_ENVIRONMENT |
The environment's name — production, staging, pr-42. |
VALLIC_ENVIRONMENT_TYPE |
What kind it is: production, staging, development or preview. The value to switch behaviour on — error verbosity, an environment indicator, whether to send real mail. |
VALLIC_SLUG |
The environment's slug, {project}-{environment}, unique across the platform. |
VALLIC_HOSTNAMES |
Every hostname the edge routes to this environment, comma-separated. Trust these Host headers and no others. Absent until something routes here. |
PROJECT_BASE_URL |
https:// and the primary hostname — the one every other hostname redirects to. What to write into absolute links when no request says. Absent until something routes here. |
| Variable | What it is |
|---|---|
PORT |
What to listen on, on every interface — 8000 unless port in vallic.yaml says otherwise. Setting port moves this and the port the platform reaches together. Gunicorn reads it on its own. |
WEB_CONCURRENCY |
How many workers Gunicorn starts — 2 unless set on the python service. See Service settings. |
FORWARDED_ALLOW_IPS |
*, so Gunicorn and Uvicorn believe the proxy's X-Forwarded-For and X-Forwarded-Proto. |
| Variable | What it is |
|---|---|
VALLIC_ENTROPY |
256 random bits, base64-encoded, generated once per environment and never changed. Derive the site's own secrets from it — Drupal's hash salt, WordPress's salts — rather than committing them. Arrives through the secret channel, so it is never in a task payload. |
| Variable | What it is |
|---|---|
DB_HOST |
The database service, reachable by this name from every container. Absent when the stack runs no database. |
DB_PORT |
Its port. |
DB_DRIVER |
mysql for MariaDB, pgsql for PostgreSQL. |
DB_NAME |
The database, created on the service's first start. |
DB_USER |
The application's user — not the superuser. |
DB_PASSWORD |
Its password. Generated once with the environment and never regenerated. Arrives through the secret channel. |
DB_ROOT_PASSWORD |
The superuser's password, on engines that have one. For a migration or a repair, not for the site. |
DATABASE_URL |
The same credentials as one connection string, which is what Doctrine reads and what most Node and Go drivers accept. Arrives through the secret channel, because it carries the password. |
| Variable | What it is |
|---|---|
SMTP_HOST |
The mail relay. Port 25 from inside the stack, unauthenticated; the relay is what talks to the outside world, through the provider set as its RELAY_HOST on port 587 — direct delivery on port 25 is blocked by most cloud providers. |
The relay's provider is the opensmtpd entry under services in vallic.yaml; set its password as a secret variable, RELAY_PASSWORD, in the console. See OpenSMTPD.
| Variable | What it is |
|---|---|
VALLIC_PRIVATE_DIR |
Absolute path of the directory that is never served — Drupal's private files, Laravel's storage/app. It outlives every release. |
| Variable | What it is |
|---|---|
WEB_ROOT |
Where the live release is inside every container: /var/www/html/current. The directory above it holds every release kept for rollback. |
VALLIC_LOG_DIR |
/var/log/app, where an application writes log files it wants collected. Anything written here is shipped with the rest of the environment's logs and kept in the machine's own copy. It outlives every release. |
| Variable | What it is |
|---|---|
VALLIC_CDN_PURGE_SOCKET |
The Unix socket to send a purge through. Present only when the project has bought a CDN. |
VALLIC_CDN_PURGE_URL |
The URL to POST a purge to, through that socket. Only its path matters. |
VALLIC_CDN_PURGE_TOKEN |
Says which environment is asking. It is not a CDN credential and cannot reach another environment's cache. Arrives through the secret channel. |