Docker Compose setup for Keila, with fully custom-branded double opt-in and welcome emails, and native redirects on signup, confirmation, and failure.
Install Docker and Docker Compose:
curl -fsSL https://get.docker.com | shsudo mkdir -p /opt/keila-docker
sudo chown -R $USER:$USER /opt/keila-docker
git clone https://github.com/Afkanerd/keila-docker.git /opt/keila-docker
cd /opt/keila-dockercp .env.example .env
openssl rand -base64 48 # use this for SECRET_KEY_BASE
vim .envSet your domain, admin credentials, database password, and the
MAILER_SMTP_* values. This SMTP config is only used for Keila's own system
emails (password reset, account emails), not for forms or campaigns, see
below.
chmod 600 .env compose.yaml
mkdir -p backups && chmod 700 backupsdocker compose up -d
docker compose psLog in at http://127.0.0.1:4000 (or your domain, see
reverse proxy) with KEILA_USER/KEILA_PASSWORD.
Then, before any mail can send:
- Create a Project.
- Create a Sender under the project (its own from address and SMTP
config, separate from the container's
MAILER_SMTP_*settings). - Create a Form to collect subscribers, see below.
server {
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:4000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Backups run on the schedule in .env (default @daily) and are saved to
./backups as pg_dumpall
cluster dumps (roles and data, not just one database). Uses
postgres-backup-local.
docker exec keila_backup /backup.shWrites to ./backups/last/, daily/, weekly/, and monthly/, each with a
*-latest.sql.gz symlink to the newest file.
Cluster dumps include CREATE ROLE/CREATE DATABASE, so they must be
restored into an empty Postgres volume, not layered onto a live one. This is
a full point-in-time recovery, not a merge.
# stop app and db
docker compose stop app db
# wipe the postgres volume, this destroys current DB state
docker compose rm -f db
docker volume rm keila_db_data
# start postgres fresh, it recreates an empty keila role/db from .env
docker compose up -d db
# stream the dump in. two harmless errors are expected here:
# "role keila already exists" and "database keila already exists",
# since step 3 already created them. everything after that restores.
zcat ./backups/daily/keila-latest.sql.gz | docker exec -i keila_db psql -U keila -d keila
# bring the app back up
docker compose up -d app# logs
docker compose logs -f
# stop
docker compose down
# update
docker compose pull && docker compose up -d