product:deploy

Table of contents

  1. Introduction
  2. What a deploy does
  3. Deploying on a server without SPLENT
  4. Running a published image
  5. Stopping the deployment
  6. Deploying from CI
  7. Keeping your settings across deploys
  8. Running behind a proxy
    1. HTTPS and cookies
  9. Serving one canonical host
  10. Keeping uploads and data
  11. Using the CLI against production
  12. Reference
    1. Options
    2. Errors
  13. 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

Two ways to run the deploy bundle ON A SERVER, WITH GIT AND DOCKER ONLY git clone cp .env.example .env docker compose up -d then fill in the secrets FROM YOUR WORKSPACE splent product:deploy deploy/.env create, merge Secrets ask or generate Ports free them Pull a registry image Up and wait <product>_deploy the same containers
Both start the same Compose project from the same bundle. The command adds the steps a person would do by hand.
  1. Creates deploy/.env from deploy/.env.example, or merges the new template into it.
  2. Asks for every # required value still empty. SECRET_KEY is always generated.
  3. Checks the ports the deployment publishes, and offers to stop the containers that hold them. Your own running deployment is never a conflict.
  4. Marks the product as deployed, with SPLENT_ENV=prod in docker/.env, so the CLI works against production.
  5. Pulls the web image when it names a registry, so a new stable is never missed.
  6. 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 with product:build.
  • Docker.

Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es