Deploying with Coolify

Running an installation on a Coolify server from the published image.

Coolify is a self-hosted platform that runs Docker resources on your own servers, with a built-in proxy that issues TLS certificates. This page deploys Alba Ticket on it from the published image, with PostgreSQL managed by Coolify and everything else in one Compose stack. It assumes a Coolify server that already has a working proxy (Traefik, the default) and a project to put the resources in.

The result is one application container, RustFS for attachments, Typesense for search, and a Coolify PostgreSQL database with scheduled backups. Email goes to your SMTP provider.

Before you start

  • Two DNS names pointing at the Coolify server: one for the application (tickets.example.com below) and one for attachments (files.example.com). Browsers upload attachments straight to RustFS, so it needs its own public name; see Reverse proxy.
  • An SMTP account for login links and notifications. There is no local mail catcher in this stack.
  • The two secrets, generated on your machine and kept somewhere safe; losing CLOAK_KEY makes stored credentials unreadable:
openssl rand -base64 64 | tr -d '\n'   # SECRET_KEY_BASE
openssl rand -base64 32                # CLOAK_KEY

The published images at ghcr.io/garth/alba are public, so the server needs no registry login. Pick the version to run from Published images; latest is the newest release.

1. Create the database

In your project's environment choose New Resource and pick PostgreSQL under Databases. Keep the defaults (PostgreSQL 16 or newer) and start it. Then:

  1. On the database's page, copy the Internal URL. It looks like postgres://postgres:<password>@<container>:5432/postgres and works as DATABASE_URL as it is.
  2. Leave Make it publicly available off; the application reaches the database over the Docker network.
  3. Under Backups, add a schedule (daily is the usual choice) and an S3 destination. This is the database backup described in Backups; the rest of that page still applies to attachments and secrets.

2. Create the application stack

Choose New Resource again and pick Docker Compose Empty under Services. Paste the contents of compose.coolify.yaml and save.

Coolify reads the file and turns every ${VARIABLE:?} into a required field. Open the resource's Environment Variables page and fill them in:

Variable Value
DATABASE_URL The Internal URL copied from the database.
PHX_HOST The application's host name, tickets.example.com, without a scheme.
SECRET_KEY_BASE, CLOAK_KEY The two secrets generated above.
SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_TLS Your mail provider; see Configuration.
MAIL_FROM The sender address, on a domain the provider lets you send from.
STORAGE_PUBLIC_BASE_URL https://files.example.com, the attachments host name.
ALBA_VERSION Optional. The image tag to run, for example 0.7.8, this release; leave it unset for latest.

Coolify generates SERVICE_PASSWORD_RUSTFS and SERVICE_PASSWORD_TYPESENSE itself and keeps them across redeploys; leave them alone. REGISTRATION_MODE defaults to invite only.

The pasted file is the whole of what the containers see. Coolify substitutes a variable from the Environment Variables page into the ${...} references in the file and nowhere else, so a variable the file does not name never reaches the application, and nothing tells you. To set one of the other variables from Configuration — PLAUSIBLE_DOMAIN, CSP and the others — add a line for it to the file's x-app-environment block first, as NAME: ${NAME:-}, and the page then shows a field for it.

Then, still on the stack:

  1. In the Domains field for the app service enter https://tickets.example.com. For the rustfs service enter https://files.example.com:9000; the port tells the proxy to route the public name to RustFS's API port. Both must match the values you put in PHX_HOST and STORAGE_PUBLIC_BASE_URL.
  2. Turn on Connect To Predefined Network so the stack can reach the database resource by its internal name.
  3. Deploy. After changing that setting or an environment variable later, use Redeploy rather than Restart: both reach the containers only when Coolify recreates them.

The migrate service runs the database migrations and exits, and the app service starts once it has finished and RustFS and Typesense answer; the application creates the attachments bucket itself on first start. The proxy requests certificates for both names as soon as DNS resolves to the server. Watch the deployment log, then the app container's log, for the line saying the endpoint is listening.

3. First visit

Open https://tickets.example.com. An installation with no users shows the setup page: it creates the first administrator, with a password so that login works before email delivery is confirmed, and names the installation. Attachment storage is registered automatically on first start from the RustFS service, so uploads work at once; check under Administration, Storage that the location verifies.

Send yourself an invitation from Invitations to confirm mail delivery, and run Rebuild index on the Search page under Administration if search returns nothing (a fresh Typesense holds no data until the first index run).

Upgrading

  1. Take a database backup from the database's Backups page (or wait for the scheduled one).
  2. Set ALBA_VERSION to the new release tag on the Environment Variables page, or leave it on latest and pull.
  3. Press Redeploy. Coolify pulls the image, migrate runs the new migrations once, and app restarts only after they succeed. If migration fails, the old application container keeps running; the deployment log and docker logs of the migrate container show why.

The notes in Upgrades about specific versions apply here too.

Importing from Jira

Upload the backup under Administration, Jira import. It goes straight from the browser to the storage bucket, under an imports/ key of its own, and the import reads it from there; nothing has to be copied onto the server. An upload is removed after a day.

Operating notes

  • Health. The image carries a health check on /health, and Coolify shows the container as healthy or unhealthy from it. The proxy stops routing to an unhealthy container.
  • Logs. The Logs tab on the stack shows every service; the application logs structured text on stdout. Look there first for Postgrex connection errors, Oban job failures and mail delivery errors.
  • Attachments live in the rustfsdata volume; include it in your backup plan as described in Backups. RustFS's console is off in this stack; to look inside the bucket, point an S3 client such as rclone at https://files.example.com with the key alba and the value of SERVICE_PASSWORD_RUSTFS.
  • Search data is derived and needs no backup; a new Typesense volume is refilled with Rebuild index.
  • Resources. The application is comfortable with 1 GB of memory; RustFS and Typesense want a further 512 MB each to start with. See Requirements.
  • Several servers. Coolify runs this stack on one server. Running more than one application node is described in Clustering and is not something Coolify's Compose resources do for you.

Troubleshooting

  • The deployment sits at "migrate", fails with service "migrate" didn't complete successfully, or the app never becomes healthy. Almost always DATABASE_URL: confirm the stack has Connect To Predefined Network on and the URL is the database's Internal URL, not the public one. Coolify hides containers that have exited, so read the reason on the server with docker logs and the migrate container's name from the deployment log. non-existing domain - :nxdomain there means the stack is not on the database's network: turn the setting on and Redeploy, and check that docker network inspect coolify lists both the database and the stack's containers.
  • error from registry: unauthorized while pulling the image. The tag in ALBA_VERSION does not exist; the registry gives the same answer for a missing tag as for a private image. Pick one from Published images.
  • Uploads fail in the browser but the storage location verifies. STORAGE_PUBLIC_BASE_URL does not match the rustfs domain, or that domain lacks the :9000 port suffix in Coolify. The application talks to RustFS internally; only browsers use the public name, and RustFS answers them only from the address in PHX_HOST, which the file passes to it as RUSTFS_CORS_ALLOWED_ORIGINS.
  • Login links point at the wrong address. PHX_HOST must equal the app domain without a scheme; PHX_SCHEME is already https.
  • Pages load but nothing updates live. The proxy must pass WebSocket upgrades; Coolify's Traefik does by default, so check that no custom proxy configuration strips them. The browser console shows the /live/websocket connection state.
  • Certificates are not issued. DNS for both names must resolve to the server and ports 80 and 443 must be open before the first deploy; the proxy's log under Servers, Proxy, Logs shows the ACME exchange.

See Troubleshooting for problems that are not specific to Coolify.