Backend - Strapi 5
Setup & Configuration
Strapi runs as a Docker image built on the staging server and transferred to production. The image is built from the backend GitHub repository using a two-stage Dockerfile:
- Build stage — installs all dependencies, compiles the Strapi Admin Panel using Vite with
NODE_OPTIONS=--max-old-space-size=4096 - Runtime stage — copies only the compiled output into a slim
node:22.23.1-bookworm-slimimage. The Infisical CLI is installed here to handle secrets injection at startup.
The final image runs as the unprivileged node user and exposes port 1337.
Secrets Management
Strapi requires a number of secrets at runtime (JWT keys, database credentials, API keys etc.). These are not stored on the server — they are fetched from Infisical at container startup.
How it works
On every container startup, the entrypoint script (docker-entrypoint.sh) uses the Infisical CLI to authenticate via Universal Auth and obtain a fresh access token, then fetches all secrets before Strapi starts:
#!/bin/sh
set -e
INFISICAL_TOKEN=$(infisical login \
--method=universal-auth \
--client-id="$INFISICAL_CLIENT_ID" \
--client-secret="$INFISICAL_CLIENT_SECRET" \
--plain --silent)
export INFISICAL_TOKEN
exec infisical run --projectId="$INFISICAL_PROJECT_ID" --env="$INFISICAL_ENV" \
-- node node_modules/@strapi/strapi/bin/strapi.js startThis means a fresh token is retrieved on every container start — no long-lived tokens are stored on the server.
docker-compose environment
The following environment variables are written to docker-compose.yml by Ansible:
| Variable | Purpose | Source |
|---|---|---|
INFISICAL_ENV | Which environment to fetch (staging / prod) | group_vars/{staging,prod}/vars.yml |
INFISICAL_PROJECT_ID | Infisical project identifier | group_vars/klhhh/vars.yml |
INFISICAL_CLIENT_ID | Universal Auth client ID for the machine identity | group_vars/klhhh/vars.yml |
INFISICAL_CLIENT_SECRET | Universal Auth client secret | group_vars/klhhh/vault.yml (encrypted) |
Infisical project
- Project ID:
23a2a063-4fd7-42b3-85b8-4f9e2d6b2bec - Project slug:
klh-3-website-a-s48 - Machine identity client ID:
6736aa1c-0cd6-4c8b-84cf-1d6945237ff4 - Machine identity client secret: stored in
group_vars/klhhh/vault.yml
Secrets stored in Infisical
APP_KEYS,API_TOKEN_SALT,ADMIN_JWT_SECRET,TRANSFER_TOKEN_SALT,JWT_SECRET— standard Strapi security tokensDATABASE_CLIENT,DATABASE_HOST,DATABASE_PORT,DATABASE_NAME,DATABASE_USERNAME,DATABASE_PASSWORD— MySQL connectionMAILGUN_API_KEY— Mailgun API key for sending emailsMETABASE_SECRET_KEY— key for embedding Metabase reporting iFramesGOOGLE_WALLET_ISSUER_ID,GOOGLE_WALLET_CLIENT_EMAIL,GOOGLE_WALLET_PRIVATE_KEY— Google Wallet integration for member cardsPUBLIC_URL,ALLOWED_ORIGINS,HOST,PORT— server URL and CORS configuration
WARNING
PUBLIC_URL must be correctly set in Infisical, otherwise the Strapi backend is not accessible from the frontend.
Deployment
Deployment is fully managed by Ansible. See Deployment for the full workflow.
In summary, deploy.yml handles:
- Building the Docker image on staging (includes Vite Admin Panel compilation)
- Fetching a fresh Infisical access token and writing it into
docker-compose.yml - Restarting the container on staging (if
target=staging) - Transferring the image to production and updating
docker-compose.ymlon prod (iftarget=prod) - On prod, the container is restarted manually after the transfer
# Deploy to staging
ansible-playbook playbooks/deploy.yml -e target=staging --tags strapi
# Deploy to production
ansible-playbook playbooks/deploy.yml -e target=prod --tags strapi
# then restart manually on prod:
# docker compose -f ~/docker/strapi/docker-compose.yml up -dTIP
The Admin Panel is compiled into the Docker image at build time. There is no in-container rebuild step — a new image must be built and deployed for any Admin Panel or data model changes.
DANGER
Always purge the Cloudflare cache after deploying a new frontend version that depends on backend API changes.
Backup & Restore
The complete Strapi setup is backed up using the inherent Strapi backup tool. The backup process runs via
~/scripts/strapi/backup_strapi.sh, executed by acronjob daily at 3:15am. The encrypted backup file is stored at~/cloud/google/Backups/strapi_backup.tar.gz.enc.INFO
Whilst this backup file contains most of the data stored in Strapi it is not complete (e.g., users/roles tables are missing), which is why the database is also backed up separately. On the other hand, the Strapi backup file is the only way to correctly restore media files in the
/uploadsfolder. Both backups are required for a full restore.The complete MySQL database including
strapi_dbis also backed up. For further details see.
