Skip to content

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:

Terminal window
touch frontend/public/.gitkeep
git add . && git commit -m "Keep frontend/public so Dockerfile COPY succeeds" && git push
# on VPS:
cd /opt/mezzanine && git pull
cd 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:

Terminal window
cd /opt/mezzanine/deploy/docker
docker compose stop nginx
rm -rf htpasswd
docker run --rm -it httpd:2.4-alpine htpasswd -nB admin > htpasswd
docker 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:

Terminal window
cd /opt/mezzanine && git pull
cd deploy/docker
docker compose stop backend
rm -f /var/lib/docker/volumes/mezzanine_data/_data/mezzanine.db*
docker compose --env-file .env build backend
docker 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:

  1. Disable basic auth (recommended — Mezzanine’s own session login is stronger): comment out the two auth_basic lines in nginx.conf and restart nginx.
  2. 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:

Terminal window
docker compose ps
docker compose logs --tail 50 backend
docker compose logs --tail 50 frontend

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

Terminal window
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:

Terminal window
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:

Terminal window
cd /opt/mezzanine/deploy/docker
docker run --rm httpd:2.4-alpine htpasswd -nbB admin 'NEW-PASSWORD' > htpasswd
docker compose exec nginx nginx -s reload

Lost the Mezzanine admin password

This is harder — there’s no email-recovery flow yet. Direct DB fix as a stopgap:

Terminal window
# Generate a new bcrypt hash for your chosen password
docker exec mezzanine-backend node -e "const b=require('bcrypt'); console.log(b.hashSync('NEW-PASSWORD',12));"
# Paste the resulting hash into the DB
docker 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.