Skip to content

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-slim image. 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:

sh
#!/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 start

This 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:

VariablePurposeSource
INFISICAL_ENVWhich environment to fetch (staging / prod)group_vars/{staging,prod}/vars.yml
INFISICAL_PROJECT_IDInfisical project identifiergroup_vars/klhhh/vars.yml
INFISICAL_CLIENT_IDUniversal Auth client ID for the machine identitygroup_vars/klhhh/vars.yml
INFISICAL_CLIENT_SECRETUniversal Auth client secretgroup_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 tokens
  • DATABASE_CLIENT, DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD — MySQL connection
  • MAILGUN_API_KEYMailgun API key for sending emails
  • METABASE_SECRET_KEY — key for embedding Metabase reporting iFrames
  • GOOGLE_WALLET_ISSUER_ID, GOOGLE_WALLET_CLIENT_EMAIL, GOOGLE_WALLET_PRIVATE_KEY — Google Wallet integration for member cards
  • PUBLIC_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:

  1. Building the Docker image on staging (includes Vite Admin Panel compilation)
  2. Fetching a fresh Infisical access token and writing it into docker-compose.yml
  3. Restarting the container on staging (if target=staging)
  4. Transferring the image to production and updating docker-compose.yml on prod (if target=prod)
  5. On prod, the container is restarted manually after the transfer
bash
# 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 -d

TIP

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

  1. 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 a cron job 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 /uploads folder. Both backups are required for a full restore.

  2. The complete MySQL database including strapi_db is also backed up. For further details see.

Released under the MIT License.