Deployment
Install with Docker Compose, create the admin, put it behind a domain
Install
mkdir iforevents && cd iforevents
curl -fsSLO https://raw.githubusercontent.com/innovafour/iforevents-selfhosted/main/docker-compose.yml
curl -fsSLO --create-dirs --output-dir clickhouse https://raw.githubusercontent.com/innovafour/iforevents-selfhosted/main/clickhouse/iforevents.xml
docker compose up -dThat is the whole install. Compose pulls the public images, creates the
network and the volumes, waits for ClickHouse and Redis to be
healthy, starts the api (which runs its migrations) and then the dashboard.
No .env is needed: every value has a working default, and the api
generates its session secret on first boot into the api_data volume.
Check it:
docker compose ps
curl -s http://localhost:8000/ready
# {"components":{"clickhouse":"ok"},"status":"ok","version":"1.0.0"}First login
Open http://localhost:8010. A fresh server shows the setup form: your name, e-mail, a password (12+ characters, three character classes) and the organization name. That creates the administrator account and signs you in; the onboarding then asks for the first project and shows the SDK snippet with your project key filled in.
The setup form appears exactly once. Afterwards signup is closed
(SIGNUP_MODE=bootstrap), and new people are added from Settings →
Team: the account is created with a one-time password you hand over, and
they change it after their first login. Without SMTP the server sends no
e-mail at all; with SMTP_* configured, verification and password-reset
mails work as usual.
Unattended installs can create the admin from the environment instead:
cat > .env <<'ENV'
[email protected]
BOOTSTRAP_ADMIN_PASSWORD=a-long-password-you-will-change
ENV
docker compose up -dThe pair is used only while the server has no account, so it can stay in the file.
Reach it from other machines
By default the dashboard and the api bind to every interface, but the
browser must be told where they are. On a LAN box at 192.168.1.10:
cat > .env <<'ENV'
PUBLIC_URL=http://192.168.1.10:8010
API_PUBLIC_URL=http://192.168.1.10:8000
ENV
docker compose up -dPUBLIC_URL is the dashboard origin (CORS, e-mail links); API_PUBLIC_URL
is where your apps send events and what the SDK snippets show. Plain http
is fine on a private network; session cookies are then not marked Secure,
which is the trade-off of having no certificate.
A domain with HTTPS
The overlay docker-compose.caddy.yml puts Caddy in front of both services
on one domain with an automatic Let's Encrypt certificate. Point the DNS
record at the host, open ports 80 and 443, then:
curl -fsSLO https://raw.githubusercontent.com/innovafour/iforevents-selfhosted/main/docker-compose.caddy.yml
curl -fsSLO https://raw.githubusercontent.com/innovafour/iforevents-selfhosted/main/Caddyfile
cat >> .env <<'ENV'
DOMAIN=analytics.example.com
[email protected]
PUBLIC_URL=https://analytics.example.com
API_PUBLIC_URL=https://analytics.example.com
BIND_ADDRESS=127.0.0.1
TRUSTED_PROXY_CIDRS=172.28.0.0/16
ENV
docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -dCaddy routes /v1/*, /sdk/*, /mcp and /.well-known/* to the api and
everything else to the dashboard, so SDKs and browsers share one origin and
the browser SDK (/sdk/v1/iforevents.min.js) loads from your domain, never
from iforevents.com.
BIND_ADDRESS=127.0.0.1 takes the two plain-http ports off the public
interface, and TRUSTED_PROXY_CIDRS lets the api trust the client address
Caddy forwards, so rate limits and the audit log see people instead of the
proxy.
Your own reverse proxy
Already running nginx or Traefik? Keep the base compose, set
BIND_ADDRESS=127.0.0.1, proxy PUBLIC_URL to 127.0.0.1:8010 and
API_PUBLIC_URL to 127.0.0.1:8000 (or route /v1, /sdk, /mcp and
/.well-known of a single host to 8000), forward X-Forwarded-For, and
put the proxy's address in TRUSTED_PROXY_CIDRS.
Production checklist
PUBLIC_URLandAPI_PUBLIC_URLare https, so cookies carrySecure.DB_PASSWORDandTOKEN_SECRETset in.envwhen the Docker host is shared with people you do not fully trust; the defaults are only reachable inside the Docker network.IFOREVENTS_VERSIONpinned to a release instead oflatest.EVENTS_RETENTION_DAYSdecided; 0 keeps everything.- A backup of ClickHouse and
api_datascheduled and restored once (Operations). SMTP_*configured if you want password resets by e-mail.