Keep it running
The app works in your terminal. Now comes the traditional next step: making it work when your terminal is closed.
Run the notebook under systemd
This Linux example assumes the repository is deployed at /srv/wred, dependencies are installed in /srv/wred/.venv, and a dedicated wred user and group can read the application. Create those resources before enabling the service. Run behind either proxy from the previous chapters.
Put this unit at /etc/systemd/system/wred.service. Its ExecStart uses Weft’s explicit loopback forwarding trust.
[Unit]
Description=Wred notebook
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
User=wred
Group=wred
WorkingDirectory=/srv/wred
EnvironmentFile=/etc/wred/notebook.env
Environment=PYTHONUNBUFFERED=1
ExecStart=/srv/wred/.venv/bin/weft example_app.asgi:app --host 127.0.0.1 --port 8000 --workers 2 --lifespan on --forwarded-allow-ips 127.0.0.1
Restart=on-failure
RestartSec=3
TimeoutStopSec=45
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.targetRestart=on-failure restarts a failed process. Systemd supervises it; it doesn’t check whether your database credentials are good or your application is emotionally ready. See systemd.service.
Keep environment settings outside the checkout
Create /etc/wred/notebook.env with your database settings. The values below target the local development database; replace them for your deployment. Restrict access if the URI contains credentials.
MONGODB_URI=mongodb://127.0.0.1:27017
MONGODB_DATABASE=wred_notebooksudo systemctl daemon-reload
sudo systemctl enable --now wred
sudo systemctl status wred
sudo journalctl -u wred -fKeep production credentials out of Git and shell history. The service manager reads the environment file; this example does not require a dotenv package.
Choose workers with the database in mind
The unit starts two workers as an example. Each worker gets its own MongoDB client and connection pool. More workers mean more pools and separate in-memory rate-limit buckets. Benchmark your workload, including database calls and middleware, before multiplying workers because the CPU graph had room for more colors.
Keep MongoDB reachable and authenticated appropriately, back up the data, and verify that you can restore it. A volume is persistence; it is not a backup with a more reassuring name.
The notebook’s health endpoint returns a simple application response. It does not ping MongoDB on every request. Use startup checks, database monitoring, and a deliberately designed readiness check for your own deployment.
When it doesn’t work
| Symptom | Check first |
|---|---|
| 502 from the proxy | Is the backend running on the same host and port? Check the service log and request the backend’s health endpoint locally. |
| Startup failure | Can the service reach MongoDB? Did its virtualenv install PyMongo and ServeStatic? Is the working directory the repository root? |
| 403 at login or refresh | The custom header, same-origin browser request, public Host, and forwarded HTTPS scheme. |
| 401 on note requests | The access token, its expiry, and the login’s existence. The refresh cookie alone does not authorize CRUD routes. |
| Static page instead of JSON | A static catch-all or SPA fallback caught an API path. Put API routing first and inspect prefix stripping. |
| Secure cookie missing over HTTPS | The ASGI scheme is probably still HTTP. Fix trusted proxy handling. |
| 429 changes between requests | The notebook limiter is per worker. Use a shared limiter for a shared policy. |
| SSE arrives all at once | Client and proxy buffering. Use curl -N and inspect the streaming location. |
| Different frontend port fails | It is a different origin. Use a development proxy or design CORS and auth together. |
Make it your application
Add routes in a module that exposes register_routes(app, ...). Pass database and service objects explicitly, as the notebook does. Put shared HTTP behavior in middleware and resource lifecycle in the outer app.
For blocking libraries, use an appropriate worker thread or replace them with an async client. Don’t start untracked tasks inside request handlers and call it a job queue. Durable work needs a durable queue and workers that own it.
You now have routes, data, auth, a frontend, middleware, HTTPS, and a supervised process. Wred is still small. Your app was allowed to grow without filing a feature request.
