Skip to content

Self-Hosting

Run Kitbase on your own server in minutes with our interactive setup script.

Prerequisites

  • A server running Ubuntu (the setup script is only tested on Ubuntu)
  • Git installed (sudo apt install git if not already present)
  • At least 4 GB of RAM
  • At least 20 GB of storage

Quick Start

1

Run the setup script

bash
git clone https://github.com/scr2em/kitbase-sdk.git
cd kitbase-sdk/self-host
sudo bash setup.sh

Why sudo?

The setup script installs Docker and Docker Compose if they're not already present, which requires root privileges. Running with sudo ensures the script can install dependencies and manage Docker without permission errors.

The script will check and install dependencies (Docker, Docker Compose), walk you through the configuration, generate your .env file, and start all services.

2

Open Kitbase

Once the script finishes, open your configured domain in the browser.

Architecture

Everything runs behind Caddy, which handles SSL automatically:

Configuration

The setup script walks you through each step below. All values are saved to a .env file that you can edit later.

Domain

Enter your domain name — the URL where you'll access Kitbase.

Example inputResult
(press Enter)http://localhost (default, for local testing)
kitbase.example.comhttps://kitbase.example.com (HTTPS auto-detected for real domains)
https://kitbase.example.comhttps://kitbase.example.com
192.168.1.100http://192.168.1.100 (HTTP auto-detected for IPs)

The script extracts the protocol and hostname automatically. For real domains, Caddy provisions an SSL certificate from Let's Encrypt.

SSL terminated by a load balancer

If you entered an https:// domain, the script asks whether SSL is handled by an upstream load balancer (e.g., AWS ALB, Cloudflare). If yes, Caddy listens on HTTP only and does not manage certificates — this avoids redirect loops.

Security

The script generates a random JWT secret for signing auth tokens. You can accept the generated value or provide your own.

VariableDescription
JWT_SECRETRequired. Secret key for JWT tokens. Generate one using the command below.

To generate a JWT secret, open a separate terminal window and run:

bash
openssl rand -base64 32

Copy the output and paste it as the value of JWT_SECRET.

You can customize token lifetimes by adding these variables to your .env file after setup:

VariableDefaultDescription
JWT_EXPIRATION3600000Access token lifetime in milliseconds (default: 1 hour)
JWT_REFRESH_EXPIRATION604800000Refresh token lifetime in milliseconds (default: 7 days)

For example, to set a 30-minute access token and a 24-hour refresh token:

bash
JWT_EXPIRATION=1800000
JWT_REFRESH_EXPIRATION=86400000

Database

Set the MySQL root password. This is used by both the MySQL container and the backend.

VariableDescription
DATABASE_PASSWORDRequired. MySQL root password

Email

Required for password resets, team invitations, and notifications. You can skip this during setup and configure it later in .env.

The script lets you choose between three providers:

ProviderBest for
SMTPAny SMTP service (Mailgun, SendGrid, Postmark, your own mail server)
AWS SESTeams already on AWS who want to use IAM credentials instead of SMTP
ResendSimple API-key setup with no infrastructure to manage

SMTP

bash
MAIL_PROVIDER=smtp
MAIL_FROM=noreply@yourdomain.com

SMTP_HOST=smtp.mailgun.org
SMTP_PORT=587
SMTP_USERNAME=your-username
SMTP_PASSWORD=your-password

Works with any SMTP server. Default port is 587 with STARTTLS.

VariableDefaultDescription
SMTP_HOSTSMTP server hostname
SMTP_PORT587SMTP server port
SMTP_USERNAMESMTP username
SMTP_PASSWORDSMTP password
SMTP_AUTHtrueEnable SMTP authentication
SMTP_STARTTLStrueEnable STARTTLS
SMTP_STARTTLS_REQUIREDtrueRequire STARTTLS
SMTP_CONNECTION_TIMEOUT5000Connection timeout in ms
SMTP_TIMEOUT5000Read timeout in ms
SMTP_WRITE_TIMEOUT5000Write timeout in ms

AWS SES

bash
MAIL_PROVIDER=ses
MAIL_FROM=noreply@yourdomain.com

SES_ACCESS_KEY=AKIA...
SES_SECRET_KEY=...
SES_REGION=us-east-1

Prerequisites

  1. The MAIL_FROM address (or its domain) must be verified in SES.
  2. If your SES account is still in the sandbox, recipient addresses must also be verified. Request production access to lift this restriction.
  3. The IAM user needs the ses:SendEmail permission.

Minimal IAM policy:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ses:SendEmail",
      "Resource": "*"
    }
  ]
}
VariableDefaultDescription
SES_ACCESS_KEYAWS access key ID
SES_SECRET_KEYAWS secret access key
SES_REGIONus-east-1AWS region for SES
SES_ENDPOINTCustom SES endpoint (optional, for testing with LocalStack)

Resend

bash
MAIL_PROVIDER=resend
MAIL_FROM=noreply@yourdomain.com

RESEND_API_KEY=re_xxxxxxxxx

Prerequisites

  1. Create an API key at resend.com/api-keys.
  2. The domain in MAIL_FROM must be verified in Resend. For testing, use onboarding@resend.dev.
VariableDefaultDescription
RESEND_API_KEYResend API key (starts with re_)

Common email variables

VariableDefaultDescription
MAIL_PROVIDERsmtpEmail provider: smtp, ses, or resend
MAIL_FROMnoreply@localhostSender email address
MAIL_INVITATION_SUBJECTYou've been invited to join an organizationInvitation email subject line
MAIL_BASE_URLhttp://localhostBase URL used in email links

Switching providers

To switch from one provider to another:

  1. Edit your .env and change MAIL_PROVIDER to the new provider (smtp, ses, or resend).
  2. Add the required variables for the new provider.
  3. You can leave the old provider's variables in place — they will be ignored.
  4. Restart the backend:
bash
docker compose up -d backend

Verifying email delivery

After configuring your provider, test by inviting a team member or resetting a password. Check the backend logs for confirmation:

bash
docker compose logs -f backend | grep -i email

You should see lines like:

Invitation email sent successfully to: user@example.com

If emails are failing, the logs will show the error from the provider (e.g., invalid credentials, unverified sender address).

Social Login (OAuth)

Allow users to sign in with Google or GitHub. This is optional and can be configured later in .env.

To set this up, you'll need to create OAuth apps:

Google

VariableDefaultDescription
OAUTH_GOOGLE_CLIENT_IDGoogle OAuth client ID
OAUTH_GOOGLE_CLIENT_SECRETGoogle OAuth client secret
OAUTH_GOOGLE_REDIRECT_URIhttp://localhost/api/auth/oauth/google/callbackOAuth callback URL
OAUTH_GOOGLE_SCOPESopenid email profileOAuth scopes to request

GitHub

VariableDefaultDescription
OAUTH_GITHUB_CLIENT_IDGitHub OAuth client ID
OAUTH_GITHUB_CLIENT_SECRETGitHub OAuth client secret
OAUTH_GITHUB_REDIRECT_URIhttp://localhost/api/auth/oauth/github/callbackOAuth callback URL
OAUTH_GITHUB_SCOPESuser:emailOAuth scopes to request

TIP

If you're using a custom domain, update the redirect URIs to match:

bash
OAUTH_GOOGLE_REDIRECT_URI=https://kitbase.example.com/api/auth/oauth/google/callback
OAUTH_GITHUB_REDIRECT_URI=https://kitbase.example.com/api/auth/oauth/github/callback

File Storage

Kitbase uses file storage for OTA update files (app builds you push to your users). By default, files are stored locally on disk — no setup needed.

For production deployments, you can use any S3-compatible storage provider.

Supported providers

ProviderS3_ENDPOINTNotes
Local disk(default, no setup needed)Files stored on the server
AWS S3(leave empty)No endpoint needed
Cloudflare R2https://<account_id>.r2.cloudflarestorage.comNo egress fees
MinIOhttp://minio:9000Self-hosted object storage
DigitalOcean Spaceshttps://<region>.digitaloceanspaces.com
Backblaze B2https://s3.<region>.backblazeb2.com
Google Cloud Storagehttps://storage.googleapis.comVia S3-compatible XML API

S3 configuration

Add these variables to your .env file:

bash
# Required — enables S3 storage (disables local storage)
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
S3_BUCKET_NAME=my-bucket
S3_REGION=us-east-1

# Required for non-AWS providers — the S3-compatible API endpoint
S3_ENDPOINT=https://abc123.r2.cloudflarestorage.com

# Optional — custom public URL for serving files
# Use this if your files are served from a different domain (e.g., a CDN or custom domain)
S3_PUBLIC_URL=https://files.example.com

TIP

Setting S3_ACCESS_KEY is what switches Kitbase from local storage to S3. If it's empty, files are stored locally.

VariableDefaultDescription
STORAGE_LOCAL_ROOT_PATH./storageLocal file storage directory
STORAGE_LOCAL_BASE_URLhttp://localhost/api/storagePublic URL for local storage
S3_BUCKET_NAMES3 bucket name (enables S3 storage)
S3_REGIONus-east-1AWS region (use auto for Cloudflare R2)
S3_ACCESS_KEYS3 access key (setting this enables S3 storage)
S3_SECRET_KEYS3 secret key
S3_ENDPOINTCustom S3-compatible API endpoint. Required for non-AWS providers
S3_PUBLIC_URLCustom public URL prefix for stored files. Overrides the default AWS URL format

Provider setup guides

Cloudflare R2
  1. In the Cloudflare dashboard, go to R2 Object Storage and create a bucket.
  2. Go to R2Manage R2 API TokensCreate API token.
  3. Copy the Access Key ID, Secret Access Key, and your Account ID (shown in the R2 dashboard URL).
bash
S3_ACCESS_KEY=your-r2-access-key
S3_SECRET_KEY=your-r2-secret-key
S3_BUCKET_NAME=my-bucket
S3_REGION=auto
S3_ENDPOINT=https://<account_id>.r2.cloudflarestorage.com

To serve files from a custom domain, enable Public Access on the bucket and set:

bash
S3_PUBLIC_URL=https://files.example.com
MinIO

If you're already running MinIO (or want to add it to your Docker Compose setup):

bash
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_BUCKET_NAME=kitbase
S3_REGION=us-east-1
S3_ENDPOINT=http://minio:9000
S3_PUBLIC_URL=http://your-server:9000/kitbase
DigitalOcean Spaces
  1. In the DigitalOcean dashboard, create a Space.
  2. Go to APISpaces KeysGenerate New Key.
bash
S3_ACCESS_KEY=your-spaces-key
S3_SECRET_KEY=your-spaces-secret
S3_BUCKET_NAME=my-space
S3_REGION=nyc3
S3_ENDPOINT=https://nyc3.digitaloceanspaces.com
S3_PUBLIC_URL=https://my-space.nyc3.digitaloceanspaces.com
Google Cloud Storage

GCS offers an S3-compatible XML API using HMAC keys:

  1. In the Google Cloud Console, go to Cloud StorageSettingsInteroperability.
  2. Create an HMAC key for a service account.
bash
S3_ACCESS_KEY=GOOG1E...
S3_SECRET_KEY=your-hmac-secret
S3_BUCKET_NAME=my-gcs-bucket
S3_REGION=auto
S3_ENDPOINT=https://storage.googleapis.com
S3_PUBLIC_URL=https://storage.googleapis.com/my-gcs-bucket

How S3 storage works

When S3_ACCESS_KEY is set, Kitbase uses the AWS S3 SDK with the configured endpoint. The SDK speaks the same S3 protocol regardless of the provider — only the endpoint URL changes.

  • S3_ENDPOINT — tells the SDK where to send API requests (uploads, downloads, deletions). Leave empty for AWS S3.
  • S3_PUBLIC_URL — controls the URL returned when referencing stored files. If not set, defaults to the standard AWS S3 URL format (https://<bucket>.s3.<region>.amazonaws.com/<path>).
  • Presigned URLs — generated using the configured endpoint, so they work correctly with any provider.

Billing

Self-hosted instances have unlimited usage with no plan restrictions.

Changing Configuration

The setup script generates two files: .env (environment variables) and Caddyfile (reverse proxy). To change anything after the initial setup, edit these files directly and restart:

bash
# Edit configuration
nano .env

# Apply changes
docker compose up -d

If you changed the Caddyfile, restart Caddy specifically:

bash
docker compose restart caddy

TIP

You can also re-run sudo bash setup.sh to go through the interactive setup again. This will overwrite your .env and Caddyfile with the new values.

Custom Domain

Point your domain's DNS to your server's IP, then re-run the setup script with https://yourdomain.com as the domain. Caddy will automatically provision an SSL certificate from Let's Encrypt.

Alternatively, update the files manually:

  1. Update your .env:
bash
APP_DOMAIN=kitbase.example.com
APP_PROTOCOL=https
MAIL_BASE_URL=https://kitbase.example.com
  1. Update your Caddyfile:
caddyfile
kitbase.example.com {
    encode gzip

    handle /api/* {
        reverse_proxy backend:8100
    }

    handle {
        reverse_proxy dashboard:80
    }
}
  1. If using OAuth, update the redirect URIs in .env:
bash
OAUTH_GOOGLE_REDIRECT_URI=https://kitbase.example.com/api/auth/oauth/google/callback
OAUTH_GITHUB_REDIRECT_URI=https://kitbase.example.com/api/auth/oauth/github/callback
  1. Restart:
bash
docker compose restart caddy

TIP

Make sure ports 80 and 443 are open in your firewall / security group. Caddy needs port 80 for the ACME challenge and HTTP → HTTPS redirect, and port 443 for HTTPS.

External Databases

By default, the Docker Compose setup includes MySQL, ClickHouse, and Redis containers. If you prefer to use managed or external instances (e.g., Amazon RDS, ClickHouse Cloud, ElastiCache), you can point Kitbase to them instead.

External MySQL

  1. Add the connection details to your .env:
bash
DATABASE_URL=jdbc:mysql://your-mysql-host:3306/flyway_db?createDatabaseIfNotExist=true&useSSL=true
DATABASE_USERNAME=kitbase
DATABASE_PASSWORD=your-password
  1. Remove the mysql service from docker-compose.yml and remove mysql from the backend's depends_on.

TIP

The database must be MySQL 8.0+. Kitbase runs migrations automatically on startup, so the database can be empty.

External ClickHouse

  1. Add the connection details to your .env:
bash
CLICKHOUSE_URL=jdbc:clickhouse://your-clickhouse-host:8123/analytics
CLICKHOUSE_USERNAME=default
CLICKHOUSE_PASSWORD=your-password
  1. Remove the clickhouse service from docker-compose.yml and remove clickhouse from the backend's depends_on.

External Redis

  1. Add the connection details to your .env:
bash
REDIS_HOST=your-redis-host
REDIS_PORT=6379
  1. Remove the redis service from docker-compose.yml and remove redis from the backend's depends_on.

WARNING

When removing a service from docker-compose.yml, make sure to also remove it from the depends_on section of the backend service, otherwise Docker Compose will fail to start.

SDK Configuration

When using the Kitbase SDK with a self-hosted instance, point baseUrl to your server's /api path:

typescript
import { init } from '@kitbase/analytics';

const kitbase = init({
  sdkKey: '<YOUR_SDK_KEY>',
  baseUrl: 'https://kitbase.example.com/api',
});

For the tracking script, load lite.js from your own dashboard host — the dashboard ships with the script bundled, so you don't need to depend on kitbase.dev:

html
<script>
  window.KITBASE_CONFIG = {
    sdkKey: 'YOUR_SDK_KEY',
    baseUrl: 'https://kitbase.example.com/api',
  };
</script>
<script defer src="https://kitbase.example.com/lite.js"></script>

TIP

The lite.js script is a lightweight loader served as a static asset by the dashboard container. It sends all data to the baseUrl you configure — your self-hosted server receives all the analytics data directly, with no external requests to Kitbase infrastructure.

Other Integrations

Slack

VariableDefaultDescription
SLACK_CLIENT_IDSlack app client ID
SLACK_CLIENT_SECRETSlack app client secret
SLACK_REDIRECT_URIhttp://localhost/api/integrations/slack/oauth/callbackSlack OAuth callback URL

Error Handling

VariableDefaultDescription
ERROR_INCLUDE_STACKTRACEfalseInclude stack traces in API error responses
ERROR_NOTIFICATION_ENABLEDfalseSend email notifications on errors
ERROR_NOTIFICATION_EMAILEmail address for error notifications

Logging

VariableDefaultDescription
LOG_LEVEL_APPINFOApplication log level
LOG_LEVEL_JOOQINFODatabase query log level
LOG_LEVEL_SECURITYWARNSecurity log level

Managing Data

Backups

Back up your data by dumping the Docker volumes:

bash
# MySQL
docker exec kitbase_mysql mysqldump -u root -p flyway_db > backup.sql

# ClickHouse
docker exec kitbase_clickhouse clickhouse-client --query "SELECT * FROM analytics.custom_events FORMAT Native" > events.backup

Data Volumes

VolumeContents
mysql_dataUser accounts, projects, feature flags, settings
clickhouse_dataAnalytics events, sessions, pageviews
redis_dataCache and rate limiting data

Updating

When a new version of Kitbase is released, update your self-hosted instance by pulling the latest images and restarting:

bash
cd ~/kitbase-sdk/self-host

# Pull latest changes (e.g. docker-compose.yml, setup script)
git pull

# Pull latest images
docker compose pull

# Restart with new images
docker compose up -d

Your data is stored in Docker volumes and is preserved across updates. To update a specific service only:

bash
# Update just the dashboard
docker compose pull dashboard
docker compose up -d dashboard

# Update just the backend
docker compose pull backend
docker compose up -d backend

TIP

The backend may take up to a minute to start while it runs database migrations and loads resources. During this time, API requests will return 502. The dashboard will remain accessible.

Troubleshooting

Checking logs

bash
# All services
docker compose logs -f

# Specific service
docker compose logs -f backend

Backend won't start

The backend waits for MySQL, ClickHouse, and Redis to be healthy before starting. Check their health:

bash
docker compose ps

Reset everything

bash
docker compose down -v
docker compose up -d

WARNING

This deletes all data. Only use this if you want a fresh start.

All Environment Variables

Complete reference of all environment variables. All variables are configured in your .env file.

Required

VariableDescription
JWT_SECRETSecret key for JWT tokens. Run openssl rand -base64 32 in a separate terminal and paste the output.
DATABASE_PASSWORDMySQL root password

App Settings

VariableDefaultDescription
PORT80Port to expose the dashboard and API
APP_DOMAINlocalhostYour domain name (without protocol)
APP_PROTOCOLhttphttp or https
SUPPORT_EMAILsupport@localhostSupport contact email shown to users

Database

VariableDefaultDescription
DATABASE_URLjdbc:mysql://mysql:3306/flyway_db?...JDBC connection URL
DATABASE_USERNAMErootMySQL username
DATABASE_PASSWORDrootMySQL password

ClickHouse

VariableDefaultDescription
CLICKHOUSE_URLjdbc:clickhouse://clickhouse:8123/analyticsJDBC connection URL
CLICKHOUSE_USERNAMEdefaultClickHouse username
CLICKHOUSE_PASSWORDclickhouse123ClickHouse password

Redis

VariableDefaultDescription
REDIS_HOSTredisRedis hostname
REDIS_PORT6379Redis port

Released under the MIT License.