Files
2026-09-17 14:17:31 +01:00

7.0 KiB

Keila

A coop-cloud recipe for deploying Keila, to manage newsletter / mailing-list subscriptions and sending.

  • Category: Apps
  • Status: 1, alpha
  • Image: pentacent/keila, 4, upstream
  • Healthcheck: Yes
  • Backups: Yes
  • Email: Yes
  • Tests: No
  • SSO: No

Basic usage

  1. abra app new keila --secrets
  2. abra app config <app-name> and set DOMAIN, KEILA_USER, MAILER_SMTP_HOST and MAILER_SMTP_FROM_EMAIL
  3. abra app secret insert <app-name> mailer_smtp_password v1 '<your smtp password>'
  4. abra app deploy <app-name>
  5. Log in with what you set in KEILA_USER as the username, with the password from the keila_password secret which abra generated (you can read it with abra app secret ls <app-name>).

The root account is created on the first boot only. Changing KEILA_USER later does nothing.

FAQ

Setting up system mail

Keila refuses to boot without this, so it is the first thing to get right. It is also the pattern every other credential in this recipe follows.

  1. Set the plain values with abra app config <app-name>:

    MAILER_SMTP_HOST=smtp.example.com
    MAILER_SMTP_FROM_EMAIL=keila@example.com
    SECRET_MAILER_SMTP_PASSWORD_VERSION=v1 # generate=false
    

    MAILER_SMTP_USER defaults to MAILER_SMTP_FROM_EMAIL and MAILER_SMTP_PORT to 587, so set those only if your provider differs.

  2. Insert the password. The secret name is the lowercased variable name, and the version must match the line above:

    abra app secret insert <app-name> mailer_smtp_password v1 '<your smtp password>'
    
  3. Redeploy: abra app deploy --force <app-name>

  4. Check it took: abra app logs <app-name> app, then trigger a password reset to prove delivery end to end.

For a relay that authorises by network rather than by login, set MAILER_SMTP_AUTH_METHOD=none. Keila then never reads the password. Still insert a placeholder at step 2 though: the secret is declared external: true, so Swarm refuses to start the service if it does not exist, whether or not the app would read it.

Registration is closed by default

.env.sample ships DISABLE_REGISTRATION=true, and that is worth keeping. Keila is a mail sender, so an open signup form means anyone can send campaigns through your SMTP credentials and against your domain's sending reputation. Create accounts from the admin UI instead.

If you do want an open instance, set the value to exactly false, FALSE, 0 or empty. Keila treats any other value as "disable", so False and no both close registration rather than open it. Pair an open instance with compose.captcha.yml.

The two mail paths

This is a bit confusing, so it is worth being explicit. Keila sends mail through two entirely separate mechanisms, and configuring one does not configure the other.

System mail is the MAILER_* variables in your env file. Registration, password reset, contact forms. Keila refuses to boot without MAILER_SMTP_HOST and MAILER_SMTP_FROM_EMAIL, so this recipe treats them as required.

Newsletter sending is configured per project in the web UI, not in the environment. You pick a sender adapter (SMTP, Sendgrid, SES, Mailgun or Postmark) and enter its credentials there. Keila stores them in the database, so there is no overlay to enable and nothing to move into a Docker secret.

One consequence worth planning for: your database backups now carry your sending credentials. Keep them somewhere protected.

The app keeps restarting right after a deploy

Swarm has no depends_on: condition: service_healthy, so on a cold start the app exits until the database accepts connections. A few restarts is expected; it settles on its own. If it does not, check the db service: docker service ps <stack>_db.

I lost the root password

abra app secret ls <app-name> shows the generated keila_password. If the account password has since been changed in the UI, use the password reset flow instead, which needs system mail working.

How secrets work here

Keila reads no *_FILE environment variables. config/runtime.exs takes its configuration straight from the environment. So this recipe mounts a small wrapper entrypoint that reads /run/secrets/* and exports each file as the matching variable before starting the release.

The mapping is the file name, uppercased. /run/secrets/mailer_smtp_password becomes MAILER_SMTP_PASSWORD. If you add a compose overlay of your own, declare the secret with the lowercased name of the variable it feeds and it will be picked up with no other change.

The entrypoint does one extra thing. Keila takes a single DB_URL with the password embedded and offers no discrete host/user/password variables, so the entrypoint assembles that URL from the postgres_password secret. The password never has to sit in your env file.

abra app new --secrets generates the secrets it can invent, such as secret_key_base, hashid_salt and keila_password. Anything from a third party is marked # generate=false in .env.sample, which means abra creates nothing at all and you insert it yourself, as in "Setting up system mail" above. Since the compose files declare secrets external: true, enabling an overlay without inserting its secret makes the deploy fail rather than starting without it. abra app secret ls <app-name> shows what is missing.

To rotate a value later, insert it under a new version (v2), bump the matching SECRET_..._VERSION line, then redeploy with abra app deploy --force <app-name>.

Why hashid_salt is a separate secret

Left unset, Keila derives the Hashid salt from SECRET_KEY_BASE. That quietly couples the two: rotating the key base would change every public Hashid, breaking the URLs in campaigns you have already sent. This recipe generates a standalone hashid_salt so the two can move independently.

Using a managed database

Comment out the compose.db.postgres.yml line in your env file, uncomment compose.db.external.yml, and insert the whole connection string as a secret:

abra app secret insert <app-name> db_url v1 'postgres://user:pw@host:5432/keila'

Set DB_ENABLE_SSL=true as well. If the provider's certificate needs a CA bundle the system trust store does not carry, add compose.db.cert.yml and insert the PEM as db_ca_cert.

This will not run on an ARM server

Upstream publishes pentacent/keila for linux/amd64 only, so an ARM host has nothing to pull. Deploy it on x86 or build your own image from the upstream Dockerfile and point CONTAINER_REGISTRY at it.

A note on the Postgres version

This recipe pins postgres:17-alpine. That is our choice: Keila's own compose sample pins nothing at all, and its CI only ever tests against PostgreSQL 13. 17 sits well above that floor while avoiding a jump to the newest version.


For more, see docs.coopcloud.tech and Keila's configuration docs.