Troubleshooting
The exhaustive list lives in the INSTALL.md → Troubleshooting section of the main repo. Mirrored highlights below.
Install-time gotchas
Repository not found when cloning
The mezzanine repo is private; you must use the deploy-key flow described in INSTALL.md Phase 3. Plain git clone https://github.com/... returns 404 because the request is unauthenticated.
Docker build fails: target frontend: /app/public: not found
The Dockerfile expects frontend/public/ to exist. Git doesn’t track empty directories. Fix locally, push, then pull on the VPS:
touch frontend/public/.gitkeepgit add . && git commit -m "Keep frontend/public so Dockerfile COPY succeeds" && git push# on VPS:cd /opt/mezzanine && git pullcd deploy/docker && docker compose --env-file .env build/bin/sh: syntax error: unexpected "&&" in nginx logs
You’re on an old docker-compose.yml whose nginx command: used a folded scalar that Docker mis-parsed. Latest repo uses nginx-alpine’s built-in template support — no shell wrapper. git pull to fix.
htpasswd: Is a directory
You ran docker compose up before creating the htpasswd file. Docker auto-created the mount source as a directory. Fix:
cd /opt/mezzanine/deploy/dockerdocker compose stop nginxrm -rf htpasswddocker run --rm -it httpd:2.4-alpine htpasswd -nB admin > htpasswddocker compose --env-file .env up -d nginx(Or skip basic auth entirely — it’s disabled by default now.)
couldn't find env file: /root/.env
You ran docker compose --env-file .env from the wrong working directory. The .env file lives at /opt/mezzanine/deploy/docker/.env and docker compose resolves the path relative to your shell, not the compose file. Always cd /opt/mezzanine/deploy/docker first. Or add an alias: echo 'alias mzz="cd /opt/mezzanine/deploy/docker"' >> ~/.bashrc.
certbot: DNS problem: NXDOMAIN
DNS not propagated yet. dig +short @1.1.1.1 app.yourdomain.com should return your VPS IP. If not, wait 1–5 min and retry. Cloudflare proxy must be DNS only (grey cloud) during initial cert issuance — Let’s Encrypt’s HTTP-01 challenge can’t pierce the proxy.
Setup form returns 500: table admin_users has no column named role
You’re on a pre-2026-05-31 backend that had a migration ordering bug. git pull to get the fix, rebuild, then wipe the broken DB and let it recreate:
cd /opt/mezzanine && git pullcd deploy/dockerdocker compose stop backendrm -f /var/lib/docker/volumes/mezzanine_data/_data/mezzanine.db*docker compose --env-file .env build backenddocker compose --env-file .env up -d backend(You’ll lose any admin user you created — recreate via the setup screen.)
Browser keeps re-prompting for basic auth mid-session
Safari drops the cached Authorization header on XHR requests. Two options:
- Disable basic auth (recommended — Mezzanine’s own session login is stronger): comment out the two
auth_basiclines innginx.confand restart nginx. - Use Chrome or Firefox — both cache basic auth more aggressively for XHR.
502 Bad Gateway from nginx
One of the upstream containers is down or unreachable:
docker compose psdocker compose logs --tail 50 backenddocker compose logs --tail 50 frontendUsually: backend crashed on an unhandled error. Restart with docker compose restart backend — if it crashes again, share the logs in an issue.
Runtime concerns
Self-deploy workflow hangs
The backend container scheduling the run might have been killed mid-deploy. The watchdog should mark it timed-out within 30 min. Force-clear:
docker exec mezzanine-backend node -e "const db=require('better-sqlite3')('/data/mezzanine.db'); db.prepare(\"UPDATE deployment_runs SET status='failed', finished_at=CURRENT_TIMESTAMP WHERE status='running' AND created_at < datetime('now','-10 minutes')\").run();"Disk filling up
Docker build cache grows with every deploy. Run weekly:
docker system prune -af --filter "until=168h"Or set it as a cron: 0 4 * * 0 docker system prune -af --filter "until=168h" > /var/log/docker-prune.log 2>&1
Lost the basic-auth password
Just regenerate the htpasswd — it’s the outer gate, no data loss:
cd /opt/mezzanine/deploy/dockerdocker run --rm httpd:2.4-alpine htpasswd -nbB admin 'NEW-PASSWORD' > htpasswddocker compose exec nginx nginx -s reloadLost the Mezzanine admin password
This is harder — there’s no email-recovery flow yet. Direct DB fix as a stopgap:
# Generate a new bcrypt hash for your chosen passworddocker exec mezzanine-backend node -e "const b=require('bcrypt'); console.log(b.hashSync('NEW-PASSWORD',12));"
# Paste the resulting hash into the DBdocker exec mezzanine-backend node -e "const db=require('better-sqlite3')('/data/mezzanine.db'); db.prepare(\"UPDATE admin_users SET password_hash=? WHERE username='your-username'\").run('PASTE-HASH-HERE'); console.log('updated');"A proper password-reset flow is a future feature.