product:deploy
Table of contents
- Introduction
- What a deploy does
- Deploying on a server without SPLENT
- Running a published image
- Stopping the deployment
- Deploying from CI
- Keeping your settings across deploys
- Running behind a proxy
- Serving one canonical host
- Keeping uploads and data
- Using the CLI against production
- Reference
- Requirements
Introduction
product:deploy runs your product in production from its deploy bundle, deploy/, the way a server that cloned the repository runs it. It also does the chores you would do by hand. It writes deploy/.env, asks for the missing secrets, frees the ports and waits until the app answers.
splent product:build
splent product:deploy
env creating deploy/.env from deploy/.env.example
secret SECRET_KEY generated
Value required for MARIADB_PASSWORD [YpVeEV2Wrd6hFfeErFbxK3gzbMHm33t9]:
Value required for MARIADB_ROOT_PASSWORD [uhmhXDdtCW0NjzLEMD6MWVTReUIaVSHM]:
deploying my_first_app (prod)...
health waiting for app to respond...
health ready
URL: http://localhost:5591
done.
Press Enter to take each suggested value. A value only you know, such as a mail host, is asked with no suggestion. Build, deploy and release tells this command apart from product:build and product:release.
What a deploy does
- Creates
deploy/.envfromdeploy/.env.example, or merges the new template into it. - Asks for every
# requiredvalue still empty.SECRET_KEYis always generated. - Checks the ports the deployment publishes, and offers to stop the containers that hold them. Your own running deployment is never a conflict.
- Marks the product as deployed, with
SPLENT_ENV=prodindocker/.env, so the CLI works against production. - Pulls the web image when it names a registry, so a new
stableis never missed. - Starts the bundle and waits up to about 90 seconds for the app to answer.
docker compose -p <product>_deploy --project-directory deploy -f deploy/docker-compose.yml --env-file deploy/.env up -d --build
The containers come back after a reboot or a crash. Only docker stop or product:deploy --down keeps them down.
Deploying on a server without SPLENT
git clone <the product's repository> && cd <product>/deploy
cp .env.example .env
docker compose up -d
Fill in the required values in .env before you start. docker compose up refuses to start until every one is set, and names the one missing. See Deploying from a clone.
Running a published image
SPLENT_IMAGE=git.splent.io/my-org/my_first_app:stable
Once a release of the product publishes an image, the web service runs the stable tag of the first registry, and deploy/.env names it. stable moves only when product:release publishes, and a feature that deploys the product follows the same tag. A product never released builds its image from the repository.
For a private image, run docker login <registry host> on the host once. The deploy never logs in itself.
To run another image for one deploy, without touching deploy/.env, pass --image.
splent product:deploy --image my_first_app:latest
product:derive --prod does this to rehearse the image product:build just made.
Stopping the deployment
splent product:deploy --down
Runs docker compose down on the bundle. The volumes, and so the data, stay.
Deploying from CI
export MARIADB_PASSWORD=secret
export MARIADB_ROOT_PASSWORD=rootsecret
splent product:deploy --ci
--ci never prompts. A missing value comes from the environment variable of the same name. When the environment does not set it, a password, a secret or a database name is generated and reported as secret <KEY> generated. A value nobody can make up, such as a third party’s key, fails the deploy and is named.
A port held by another stack fails the deploy too, since --ci never stops other containers.
Keeping your settings across deploys
Each deploy records the template it merged in deploy/.env.base. Next time, a value you never changed follows the new template, and a value you changed stays.
| Template | Your deploy/.env |
Result |
|---|---|---|
SITE_NAME=Two, was One |
SITE_NAME=One, never edited |
Updated |
MAIL_SERVER= |
MAIL_SERVER=smtp.example.org |
Kept |
MARIADB_PASSWORD=, required |
MARIADB_PASSWORD=secret123 |
Kept |
SPLENT_ENV=prod |
missing | Added |
| gone | OLD_FEATURE_SETTING=x |
Dropped |
| not in it | GUNICORN_WORKERS=3, uncommented by you |
Kept |
The output names the keys, never their values.
env updated 1 variable(s) from the template: SITE_NAME
env kept your value for 1 variable(s) the template sets differently: MAIL_SERVER
env delete a line from deploy/.env to take the template's value
env dropped 1 variable(s) the template no longer has: OLD_FEATURE_SETTING
To take the template’s value back, delete the line and deploy again. A deleted # user-tunable key stays deleted, so the compose default applies. The optional settings you have not set come last, commented out.
Running behind a proxy
SPLENT_TRUSTED_PROXIES=1
Set SPLENT_TRUSTED_PROXIES in deploy/.env to the number of proxies whose X-Forwarded values reach the app. Without it the app takes the proxy for the caller, so every visitor shares one address, links use http:// and cookies lack Secure.
| In front of the app | Value |
|---|---|
| A TLS front on the host (nginx, Caddy, Traefik) | 1 |
| A reverse-proxy feature of the product, alone or behind a TLS front | What the feature declares, usually 1 |
| Several proxies, each adding its own values | One per proxy |
| Nothing | 0 |
The deploy never sets it for you, since trusting a proxy that is not there lets any client forge its address. While it is unset, it warns you.
SPLENT_TRUSTED_PROXIES is not set in deploy/.env, so the app believes no X-Forwarded header.
Behind a reverse proxy set it to the number of proxies in front (1 behind the usual TLS front). Set it to 0 when nothing is in front.
Change it in deploy/.env and run product:deploy again.
A proxy feature declares its number as SPLENT_TRUSTED_PROXIES = 1 in its own [tool.splent.config]. The deploy then names it in the warning, and also warns when your value is higher, since a higher value makes the app ignore the headers.
HTTPS and cookies
Cookies are Secure when the request arrived over HTTPS, which behind a proxy needs SPLENT_TRUSTED_PROXIES. SPLENT_COOKIE_SECURE=true forces it for every cookie, and false never sets it. See Cookies.
Serving one canonical host
A site reached by several names should answer by one, so its mails and OAuth callbacks name the same host and a forged Host never reaches a reset link.
class ProductionConfig(BaseProd):
def __init__(self):
super().__init__()
self.SERVER_NAME = os.environ["SERVER_NAME"]
self.TRUSTED_HOSTS = [self.SERVER_NAME, "localhost"]
Flask builds every absolute link with SERVER_NAME, and answers 400 to a Host outside TRUSTED_HOSTS. The framework sets neither, so set both in the production class of your config.py. /healthz and /readyz take any Host, so the healthcheck still works.
In front, the reverse proxy should forward only the Host it matched and redirect the site’s other names to the main one.
Keeping uploads and data
Every directory the app writes to must be the target of a named volume, or its files live in the container and go with it on the next deploy. Give the app the path from the same expression as the mount, in the product’s docker-compose.prod.yml.
environment:
PROTECTED_UPLOADS_DIR: ${PROTECTED_UPLOADS_DIR:-/workspace/<product>/protected_uploads}
volumes:
- <product>_web_deploy_protected_uploads:${PROTECTED_UPLOADS_DIR:-/workspace/<product>/protected_uploads}
The deploy warns when PROTECTED_UPLOADS_DIR or ARCHIVE_DIR is set and no volume mounts it. A copy on another machine, of the database and of every volume, is a feature’s job or the host’s, not the CLI’s.
Using the CLI against production
splent feature:notes export-notes
While the product is deployed, the CLI builds the production app. It reads deploy/.env and joins the bundle’s network, so a feature’s own commands work on the deployment. Commands that rewrite or invent data, db:seed, db:reset and db:rollback, refuse to run against production.
On the first deploy, run the first steps your features ask for this way, such as the command that creates the first account.
Reference
Options
| Option | Description |
|---|---|
--ci |
Never prompt. Missing values come from the environment or are generated, and a port conflict fails |
--down |
Stop the deployment instead of starting it |
--image REF |
Run this image for this deploy only, without changing deploy/.env |
Errors
| Message | What to do |
|---|---|
deploy/<file> not found. Run 'splent product:build' first. |
Run product:build |
Missing required variables: <keys> |
Set them in the environment, or run without --ci |
<n> port conflict(s) found |
Stop the listed containers, or answer yes to stop them |
Could not pull <image>. |
Check SPLENT_IMAGE in deploy/.env, and docker login for a private image. No container was touched |
Deployment failed., app not responding |
Read the Docker output, or docker logs <product>_web_deploy |
<VAR> is set to '<path>' but no volume ... mounts that path. |
Add a named volume for it to docker/docker-compose.prod.yml and build again |
deploy/.env and deploy/.env.base hold secrets, so they are written with mode 0600 and owned by the owner of deploy/.
Requirements
- A selected product (
SPLENT_APP), built withproduct:build. - Docker.