Maintenance¶
This page covers the recurring tasks of running a Cocotte instance: updating to a new version, backing up and restoring data, reading logs, re-running the seed commands, rotating the secret key and checking that the service is healthy.
Commands are given for the Docker (standalone) and Classic variants described in
Production deployment. The Docker examples assume you are in the directory that
contains docker-compose.prod.yml and .env.prod.
Tip
In the shared-proxy mode, add -f docker-compose.prod.proxy.yml after
-f docker-compose.prod.yml in every Docker command on this page. A shell alias saves typing:
Update to a new version¶
Back up the database first (see Back up and restore), then read the Changelog for anything that needs manual action.
-
Fetch the new code:
-
Rebuild the images and restart the containers:
When the new
backendcontainer starts, its entrypoint runsmigrateandcollectstaticbefore launching Gunicorn. The new SPA build is baked into the newfrontendimage. The database, uploaded files, static files and TLS certificates are kept in named volumes. -
Check the logs and the health endpoint (see Check health).
-
Optionally, reclaim the disk space used by old images:
-
Fetch the new code:
-
Update the backend dependencies, apply migrations and collect static files:
cd /opt/cocotte/app/backend sudo -u cocotte uv sync --frozen --no-group dev --extra prod sudo -u cocotte env DJANGO_SETTINGS_MODULE=config.settings.prod .venv/bin/python manage.py migrate --noinput sudo -u cocotte env DJANGO_SETTINGS_MODULE=config.settings.prod .venv/bin/python manage.py collectstatic --noinput -
Rebuild the frontend:
-
Restart the backend:
The web server serves the new
frontend/distimmediately; it only needs a reload if you changed its configuration. -
Check the logs and the health endpoint (see Check health).
Note
Cocotte is a PWA: users who keep the app open, or installed it, get the new frontend once the service worker has fetched the update, usually on their next visit or reload.
Back up and restore¶
A complete backup has two parts:
| What | Docker | Classic |
|---|---|---|
| Database | db service, volume postgres_data |
Your PostgreSQL database cocotte |
| Uploaded files (recipe and thematic page images) | Volume media_data |
backend/media/ |
| Configuration | .env.prod |
backend/.env, systemd unit, web server configuration |
Static files (static_data volume, backend/staticfiles/) are regenerated by collectstatic
and the TLS certificates (caddy_data volume) are re-issued automatically, so they don't need a
backup.
Docker Compose prefixes volume names with the project name, which defaults to the name of the
directory containing docker-compose.prod.yml. For a checkout in /opt/cocotte, the media
volume is cocotte_media_data. List them to be sure:
Back up the database¶
Back up uploaded files¶
Copy both files, and your configuration file, off the server. Automate this with a cron job or systemd timer, and test a restore from time to time.
Restore¶
-
Stop the backend so that nothing writes to the database during the restore:
-
Restore the database.
--clean --if-existsdrops the existing objects before recreating them: -
Restore the uploaded files:
-
Start the backend again. In Docker, migrations run on start; in Classic, run
migrateif you restored a dump taken from an older version.
Tip
To move an instance to a new server, install Cocotte there, restore the database dump and the media archive as above, copy your configuration file, and update DNS.
Note
Users can also export their own data from My account (see Your account), and a staff member can export every recipe as a portable archive (see Moderation). These archives are useful for sharing recipes between instances, but they are not a substitute for a database backup: accounts, plannings and settings are not included.
Personal data and retention¶
Accounts that have not logged in for a long time are warned, then deleted, by the
purge_inactive_users command. Backups contain personal data too: keep them for a bounded time.
See GDPR compliance.
Read the logs¶
Gunicorn writes its access log and error log to standard output, so they end up in Docker's logs or in the systemd journal.
# All services, following new lines
docker compose -f docker-compose.prod.yml --env-file .env.prod logs -f
# One service, last 200 lines
docker compose -f docker-compose.prod.yml --env-file .env.prod logs --tail 200 backend
Services are backend (Django, Gunicorn, migrations, emails printed by the console backend),
frontend (Caddy: TLS certificate issuance and renewal, proxy errors) and db (PostgreSQL).
Re-run the seed commands¶
The seed commands can be run again at any time, for example after an update that extends the ingredient library. They never create duplicates, but each one overwrites the records it manages with the seeded values:
| Command | What a re-run does |
|---|---|
seed_nutrient_requirements |
Resets every daily minimum to its seeded value, undoing edits made in the Django admin. |
seed_common_ingredients |
For each seeded ingredient (matched by name, case-insensitively), overwrites category, default unit, season, translations, nutrition, carbon footprint and allergens, and marks allergens as reviewed. Ingredients that are not part of the seed are left untouched. |
seed_cookware |
Creates the seeded cookware items that are missing (matched by name, case-insensitively), sets their English name, and fills in the emoji and photo only when they are empty (only missing photos are downloaded from Wikimedia Commons). It never deletes or renames anything. |
seed_allergens |
Resets the name of each of the 15 reference allergens. Allergens you added yourself are left untouched. |
seed_thematic_pages |
For the three seeded pages (matched by title), resets icon, description, filters and display order. It never replaces an image you uploaded, and does not change the Visible on the homepage setting. |
Warning
If you have corrected a seeded ingredient or threshold, re-running the corresponding seed command reverts your correction.
Rotate the secret key¶
Change DJANGO_SECRET_KEY if it may have leaked. Generate a new value (see
Generate a secret key), put it in .env.prod or
backend/.env, and restart the backend.
Rotating the key invalidates everything signed with the old one:
- every user is logged out of the app (login tokens are signed with the key) and must log in again;
- Django admin sessions end;
- password-reset links already sent stop working.
Calendar subscription links are random tokens stored in the database and keep working. Cocotte
does not expose Django's SECRET_KEY_FALLBACKS setting, so there is no transition period.
Check health¶
The backend exposes an unauthenticated health endpoint that answers {"status": "ok"} as long
as Django is running. It does not query the database.
To query Gunicorn directly on the server, bypassing the web server, send the headers the proxy would send; otherwise Django rejects the request (unknown host) or redirects it to HTTPS:
(Classic only: in Docker, port 8000 is not published on the host.)
Other quick checks:
docker compose -f docker-compose.prod.yml --env-file .env.prod ps
docker compose -f docker-compose.prod.yml --env-file .env.prod exec db pg_isready -U postgres
Note
ps may list backend as unhealthy: its built-in healthcheck calls the health endpoint on
localhost over plain HTTP, which the production settings reject. The public URL check above is
the reliable one.
To check the database connection and pending migrations from Django:
An empty output means the database is reachable and every migration is applied.