Deploy
One small Python service behind nginx. No container, no build step, no queue.
See the deployment view for what runs where on the box, and what it can reach outside it.
Before you start
- A Linux server with Python 3, nginx and systemd, and
sudo. - The three files in
deploy/: the systemd unit (web, loopback port 8300), the nginxlocation /dataproductfactoryblock, anddeploy.sh— install, test, restart, confirm it is up; a failing test leaves the old version serving.
Steps
First install
1. Create the service account's directory and put a checkout of the
repository in it, then install the dependencies as web:
sudo mkdir -p /srv/dataproductfactory && sudo chown web:web /srv/dataproductfactory
# copy the repository there, then, as web:
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
2. Write the environment file — root-owned and mode 600, because a unit file
under /etc/systemd is world-readable and a secret inside it would not be a
secret:
sudo install -m 600 -o root /dev/null /etc/dataproductfactory.env # then fill it in
The minimum for a deployment behind a host site's sign-in:
DPF_ENV=production
DPF_PREFIX=/dataproductfactory
SECRET_KEY=… # the site's own key, so its session cookie can be read
DPF_IDENTITY=site+link # see ../architecture/identity.md
ALLOWED_EMAILS=…
DPF_SITE_URL=https://…
DPF_LINK_KEY=… # a long random string
3. Install and start the service:
sudo cp deploy/dataproductfactory.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now dataproductfactory
4. Paste deploy/nginx-location.conf into the site's
server { listen 443 … } block, above the catch-all location /, and
reload nginx.
Releasing a change
1. Merge to the deployed branch, with the suite green locally.
2. Back up what will be replaced, including the database — see Back up and restore:
cp -a /srv/dataproductfactory/{dpf,templates,static,var/dpf.sqlite3} /root/deploy-backups/<date>/
3. Ship the code — either git pull on the server, which keeps .git so the
running build reads straight from it, or, from a checkout, write the commit into
a BUILD file next to VERSION and rsync both alongside the application:
git rev-parse --short HEAD > BUILD
rsync -a --exclude __pycache__ dpf templates static tests docs README.md CHANGELOG.md requirements.txt VERSION BUILD web@host:/srv/dataproductfactory/
Without .git on the server, BUILD is the only way the app can say which
commit is running (dpf/version.py) — skip it and the build mark goes blank,
though the version number still updates.
4. Run ./deploy/deploy.sh — it installs requirements.txt, runs the test
suite, and restarts — or do those three by hand:
.venv/bin/pip install -r requirements.txt, the suite, then
systemctl restart dataproductfactory. deploy.sh also writes BUILD from
git itself, but only when .git is there to ask — after a git pull that is
redundant and harmless; after an rsync it is a silent no-op, which is why the
step above does it by hand first.
5. Check it: the app answers, and systemctl is-active dataproductfactory.
Schema changes need no step of their own: every start adds whatever tables or
columns are missing (dpf/db.py, _migrate), so a change appears after the
restart. Nothing is ever dropped automatically.
Operating it
Every command runs as the service account, with its environment loaded:
cd /srv/dataproductfactory
set -a; . /etc/dataproductfactory.env; set +a
sudo -E -u web .venv/bin/python -m dpf <command>
| Task | Command |
|---|---|
| Add a person, or recover a locked-out administrator | user add\|invite … — see Add a person |
| Issue or revoke a read-only API token | token create\|list\|revoke … — see Issue an API token |
| Cut a release | release major\|minor\|patch — see Release a version |
| Rebuild the demo products | showcase |
| Check the warehouse connection | connect |
| Export every document as markdown | export PRODUCT --to DIR |
| Validate a workflow file before using it | workflow FILE |
Logs go to the journal: journalctl -u dataproductfactory -f.
Check that it worked
systemctl is-active dataproductfactorysaysactive.- The app answers, and the sidebar foot shows the version — and, once deployed by the rsync path above, the build — you shipped.
journalctl -u dataproductfactory -n 20has no traceback since the restart.
If it does not work
- The service refuses to start, and the journal names a variable. Startup
refuses an incomplete or contradictory identity configuration rather than
guessing — the message names what is missing or conflicting; fix
/etc/dataproductfactory.envand restart. - The site serves the old version after a deploy.
deploy.shleaves the old version running when the test suite fails — check its output, thenjournalctl -u dataproductfactory. - 404 on every page under the prefix.
nginx-location.conf'sproxy_passhas no trailing slash on purpose — adding one strips the prefix and breaks every URL the app builds withDPF_PREFIX.
Related
- Run it locally — the same application, without a server.
- Release a version — cutting the version this ships.
- Back up and restore — the database backup step above, in full.
- Rotate a secret — if anything in the environment file leaked.