Troubleshooting
Common problems and where to look.
Checking an installation
The image checks the services it depends on and says what to change for each one that is not right:
docker compose exec app /app/bin/doctor
When the application keeps restarting, run it in a container of its own instead:
docker compose run --rm app /app/bin/doctor
It checks:
- Database: that it answers and that every migration has run.
- Database indexes: that no index was left invalid by an interrupted build (see An index is invalid).
- Search: that Typesense is configured and answers.
- Attachment storage: that an object can be written, read back and deleted.
- Uploads from the browser: that the storage's CORS rules let a browser at the application's address upload. This asks the storage endpoint the server uses; whether
STORAGE_PUBLIC_BASE_URLreaches the same storage from a browser is not something the server can see. - Outgoing email: that the mail server accepts a connection, TLS and the login, without sending anything.
- Public address: that
PHX_HOSTis set. - Licence: whether it has expired or the installation has outgrown it.
Each line is [ ok ], [warn], [FAIL] or [skip], with a sentence and, for a problem, the part of this guide that explains it. The command exits with status 1 when a check failed, so a script can run it after an upgrade. It contacts only the services the installation is configured with.
Administrators see the same checks under Administration, Installation check, which also compares the address the page was opened at with the one in PHX_HOST.
Search finds nothing
The Search page under Administration shows whether Typesense is configured and reachable. "Not configured" means TYPESENSE_URL or TYPESENSE_API_KEY is unset; "unreachable" means the server did not answer, so check the container and the key. After a connection failure each application node stops trying Typesense for thirty seconds so pages do not wait on it; opening the Search page under Administration probes the server again and clears that pause as soon as it answers. When it is connected but "Indexed" is far below "Tickets", press Rebuild index; a fresh or restored Typesense starts empty. Ticket lists and pages keep working without search.
The container restarts or /health returns 503
The application cannot reach the database. Check DATABASE_URL, that the database container is healthy, and the application log for the connection error.
An index is invalid
Some upgrades build an index without locking the table, so the application keeps working while it builds. If that build is interrupted (the container stopped during bin/migrate, a deadlock, a statement timeout), PostgreSQL keeps the unfinished index under its name, marked invalid, and running the migration again skips it because the name exists. Nothing fails, but every lookup that needed the index reads the whole table instead. bin/doctor names each invalid index. Rebuild each one from psql on the application's database:
REINDEX INDEX CONCURRENTLY time_entries_author_id_index;
An invalid index whose name ends in _ccnew or _ccold is what an interrupted REINDEX ... CONCURRENTLY leaves behind; drop that one instead, with DROP INDEX CONCURRENTLY, and rebuild the index it was named after.
Login links never arrive
Without SMTP_HOST production sends nothing. Check the SMTP variables and the log for delivery errors. The bundled Mailpit at port 8025 shows what would have been sent.
Every email (login links, invitations, notifications) is sent by a background job, and a delivery the mail server refuses is retried up to five times. Each failed attempt is a log line naming the job and the reason, a warning while it will be retried and an error when it gives up:
[warning] Alba.Jobs.DeliverLoginEmail job 1042 (queue default) failed on attempt 1 of 5: {:retries_exceeded, {:network_failure, ~c"smtp.example.com", {:error, :econnrefused}}}; will retry
A message the mail server took leaves a line too, with the sender but never the recipient or the subject:
[info] Mail accepted by Swoosh.Adapters.SMTP from no-reply@example.com for 1 recipient(s)
So a request for a login link followed by neither line means the job did not run, and an "accepted" line with no email in the inbox means the problem is after Alba Ticket: the spam folder, the provider's log, or a sender the provider does not deliver for. Every email is sent from MAIL_FROM.
To see what happened to recent mail jobs, including ones from before a restart:
docker compose exec app bin/alba rpc 'import Ecto.Query; Alba.Repo.all(from j in Oban.Job, where: like(j.worker, "%Deliver%"), order_by: [desc: j.id], limit: 5, select: {j.worker, j.state, j.attempt, j.errors}) |> IO.inspect()'
completed with no errors means the mail server accepted the message: look in the spam folder, then at the provider's own log for a bounce, and check that MAIL_FROM is on a domain the provider lets you send from, with SPF and DKIM set up. retryable or discarded shows the server's answer in errors. available and never attempted means no node is running the job queue.
Attachments cannot be uploaded
- The attachments area says storage is not configured: add a storage location in Administration or set the
STORAGE_*variables and restart. - Uploads fail in the browser: the bucket's address (
STORAGE_PUBLIC_BASE_URLor the endpoint) must be reachable from the browser and its CORS policy must allowPUTfrom the application's origin. The browser console shows the blocked request. - Verification fails in Administration: the credentials, region or path-style setting are wrong for the store.
Pages do not update live
The reverse proxy is not passing WebSocket upgrades. See Reverse proxy and TLS.
Single sign-on fails
Use the Test button on the connection: it fetches the provider's discovery document and reports what is wrong. The redirect URI shown on the form must be registered with the provider exactly.
A Jira import stopped
Imports are checkpointed. Fix the cause shown in the run's error and start the import again; it continues from the last completed stage without duplicating anything. Review items list everything that needs attention.
The API answers feature_disabled
A request to /api/v1 or /mcp answered 403 with the code feature_disabled means that way in is switched off for the installation. The REST API and the MCP server are switched on separately, and both are off in a new installation until an administrator switches them on under Administration, Modules and features. The same answer on one route only (the boards routes, the CRM routes) means that module or feature is switched off there. See Modules and features.
An AI application cannot connect
The application reads /.well-known/oauth-protected-resource/mcp to find out how to sign in. That address answers 404 while the MCP server is switched off, which is the case in a new installation: switch it on under Administration, Modules and features. If it is on, check that the reverse proxy passes /mcp, /oauth and /.well-known through unchanged.
Webhooks return 401
The secret on the code host does not match the integration's. Edit the integration to set a new secret and update the webhook.
Getting the log
docker compose logs -f app
Licence notices
"This installation of Alba Ticket has no licence" on every page. No key has been entered, or the stored key no longer verifies. Get a key or a trial key from albaticket.com/licenses and paste it under Administration, Licence. See Licence.
"is not a genuine Alba Ticket licence key". The key was cut short or altered on the way. Copy it again with the portal's Copy the key button and paste the whole of it, from ALBA1. to the end; line breaks and spaces do not matter.
The notice says the installation is over its licence. A licence is for a number of seats, and the Licence page shows how many it has beside how many members can log in. Deactivate accounts that are no longer used, or ask for more seats from the licence's page in the portal; the notice goes when the members fit or the new key is saved. The count is refreshed every five minutes.
Content security policy
Every page is sent a Content-Security-Policy: it may run the application's own script, and may load from and talk to the application itself and the storage locations configured under Administration, Storage, and nothing else. The allowed storage address is worked out from each location: its public base URL and its endpoint, with the bucket in front of the host unless the location uses path-style addresses.
An upload fails, or an image in a ticket does not show, and the browser console says "Refused to connect" or "Refused to load" because of the Content Security Policy. The browser is reaching storage at an address the location does not describe, usually because a proxy or CDN sits in front of the bucket. Set the location's Public base URL to the address browsers really use. To confirm that the policy is the cause, start the application with CSP=report: the policy is then only reported in the console and blocks nothing. CSP=off removes it. Put it back once the location is right.
A reverse proxy that adds its own Content-Security-Policy header can conflict with this one; let the application's through.