Troubleshooting the installation
Troubleshooting the installation
Who this is for: the administrator of a self-hosted installation made with .
What it covers: where to look first, then faults by symptom, with the command that confirms the cause and the fix.
Before you begin you need: shell access to the server, in the release directory (the one with docker-compose.yml).
Self-hosted only. Everything here concerns the server you run. If your problem is a project setting or a right, it is for the project owner: see the .
Look here first
docker compose ps
docker compose logs --tail 100 app
curl -s http://127.0.0.1:3000/api/health
docker compose ps shows whether app, db, valkey (and caddy) are up, and how often they restarted.
The app log is on standard output. Add --since 1h for a time window, or -f to follow it. The same works for db, valkey and caddy.
The health reply names the failing part: "status":"degraded" with checks such as db or valkey set to an error text. "status":"draining" means the app is shutting down. Stack traces in the log point at compiled files, because the image has no application source. Quote log lines as they are when you ask your supplier for help.
In the browser, System, Health shows whether the background schedulers run, and Jobs shows background work.
Log messages and what they mean
Message in the app log | Meaning | What to do |
|---|---|---|
| The database is still starting, or unreachable. The app waits up to a minute. |
|
| The database role cannot create the extension. Common with a managed database. | Create the extensions as a superuser in that database ( |
| A migration stopped. | Read the message. Fix the cause and restart the app; migrations that were not applied run again. Do not edit migration files. |
| The search objects that a migration creates are absent, so migrations did not complete. | Restart the app and watch for |
| Valkey is missing, unreachable, or refuses the password. The app stops on purpose. |
|
|
| Set a real value ( |
| The | The message lists the valid ids. Fix or remove the variable. |
| The stall detector could not load its native module. | A warning only; the app runs. Report it to your supplier if it persists on the bundled image. |
| The orderly shutdown is cut short when the container stops. |
The installer says the app did not become healthy
install.sh waits six minutes for /api/health and then prints this. The stack is usually still running.
Read the log for one of the messages above.
docker compose logs --tail 120 app
If the log ends with [migrate] up to date and the server started, wait a minute and test the health address again. A first start on a small host can be slow.
Do not edit docker-compose.yml to get past the error. Fix the cause, then run docker compose up -d.
Check: curl -fsS http://127.0.0.1:3000/api/health returns "status":"ok".
The database rejects the app after you changed POSTGRES_PASSWORD
The database image reads POSTGRES_PASSWORD only when it creates a new data volume. Changing the value in .env later does not change the password inside the existing database, so the app gets password authentication failed.
Put the old value back in .env, then docker compose up -d; or
Change it inside the database and in .env together:
docker compose exec -T db psql -U codedocs -d codedocs -c "ALTER USER codedocs WITH PASSWORD 'NEW_PASSWORD'"
Then set POSTGRES_PASSWORD=NEW_PASSWORD in .env and run docker compose up -d.
HTTPS and the certificate
Symptom | Cause | Fix |
|---|---|---|
The browser cannot connect to | The domain does not point at this server, or ports 80/443 are blocked |
|
| DNS is wrong or not yet visible; a stale | Fix DNS (use DNS-only while the certificate is issued), then |
| Another web server holds 80 or 443 | |
The certificate is requested again and again after a restart | The | Keep that volume. Losing it re-issues the certificate and runs into the authority's limits. |
Check the certificate (subject is your domain, issuer is a public authority, notAfter about 90 days ahead):
echo | openssl s_client -connect app.example.com:443 -servername app.example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates
Signing in
Symptom | Fix |
|---|---|
You lost the | In System, Users, select |
You lost the | Clear the stored password and restart the app. At start the app repairs the account and sets its password to the |
Everybody was signed out |
|
Passkeys stop working after you moved to a new domain | Passkeys are bound to the domain in |
For the second row:
docker compose exec -T db psql -U codedocs -d codedocs -c "UPDATE users SET password_hash = NULL WHERE email = 'admin'"
docker compose restart app
Check: grep '^ADMIN_INITIAL_PASSWORD=' .env, then sign in as admin with that value and change it.
Emails
Symptom | Cause and fix |
|---|---|
A new account shows Account created but failed to send verification email | Outgoing email is not configured or fails. In System, Config, Email, fill the provider, set the From address, press Test Connection. Read the error it prints. |
Test fails with a TLS or protocol error on SMTP | Port 465 uses implicit TLS; 587 and others use STARTTLS. Match the port to your provider. |
Resend test passes but mail is rejected | The domain of From address is not verified in Resend (SPF and DKIM). |
Emailed links point to | Public App URL is wrong or empty. A value saved in Config wins over |
Inbound mail never arrives | The webhook address |
Files and uploads
Symptom | Cause and fix |
|---|---|
Uploading a large file fails at once, with a |
|
Uploads fail with Upload failed | Config, Storage & Logs, Test Connection. For S3, check endpoint, region, bucket, keys, and Force path-style addressing for MinIO. Check free disk with |
Images disappear after a rebuild of the stack | You ran |
Switching to S3 did not move the old files | Switching affects new uploads only. Use Migrate storage to copy existing files. |
Live editing, speed and background work
Accounts and plans
Symptom | Fix |
|---|---|
A new user hits Plan limit reached at once (2 projects, 20 documents) | The account is on the Free plan. Check that |
Backups are a paid feature | The account's plan has backups off. The Self-hosted plan has them on. |
Available plans is empty for a user | No plan is offered for sale and Stripe billing is not set up. Expected on a self-hosted installation. |
Disk and logs
docker system df shows what Docker uses. Images of old releases stay until you remove them: docker image prune (only unused images).
Docker keeps container logs without a size limit by default, so a busy installation can fill the disk. Set a limit once in /etc/docker/daemon.json and restart Docker:
{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "5" } }
The setting applies to containers created after the restart; run docker compose up -d --force-recreate to apply it to the stack.
Rolling back an update
There is no downgrade after the migrations ran. To go back, stop the stack, restore the database dump taken before the update into a fresh database volume, and start the previous release. The procedure and the backup contents are in .
Starting over on a failed first install
Only when the installation holds no data you need.
Careful:
docker compose down -vdeletes the database, Valkey data and uploaded files permanently.
docker compose down -v
./install.sh --domain app.example.com
The existing .env is reused, so the secrets stay the same.