Kutup

Self-hosting

Run your own Kutup in minutes

Kutup is self-hosted by design. A production deployment is a Docker Compose stack — backend, database, encrypted storage, and Nginx. Here’s the quick start; the full guide lives in the docs.

Prerequisites

  • Docker 24+ and Docker Compose v2 (the docker compose command).
  • A Linux server with at least 1 GB of RAM.
  • A domain name — required for HTTPS and for federation to work correctly.

1. Clone and configure

Clone the repo and create your environment file:

sh
git clone https://github.com/kutupbt/kutup.git
cd kutup
cp .env.example .env

Edit .env and fill in every value with strong secrets:

.env
# PostgreSQL — use a strong random password
POSTGRES_PASSWORD=<strong-random-password>

# JWT secret — generate with: openssl rand -hex 64
JWT_SECRET=<64-byte-hex-string>

# SeaweedFS S3 credentials — Compose injects them into every service
S3_ACCESS_KEY=kutup
S3_SECRET_KEY=<strong-random-secret>
S3_BUCKET=kutup-files

# Stable account/federation domain — set before creating accounts
CHAT_SERVER_NAME=kutup.example.com

# Public URL — used to build federation invite links
SERVER_URL=https://kutup.example.com

# Break-glass admin bootstrap — a single email:username:password triple.
# This account can never be demoted, disabled, or deleted; promote any
# further admins from inside the app.
ADMIN_ACCOUNT=admin@example.com:admin:<strong-admin-password>

Compose injects the S3 credentials into SeaweedFS and the backend; there is no second credential file to edit.

2. Start the stack

Build and launch all services — Postgres, SeaweedFS, backend, frontend, and Nginx:

sh
docker compose up -d --build --wait

3. First login

Find the admin bootstrap confirmation in the logs, then open your domain and log in:

sh
docker compose logs backend | grep -i "admin\|bootstrap"

On first login you’ll generate your 24-word recovery phrase (write it down — it’s the only way to recover your account and is never sent to the server) and can optionally enable 2FA.

4. Add TLS

The bundled Nginx already requires TLS. Issue a certificate, place fullchain.pem and privkey.pem in nginx/certs/, and restart or reload Nginx:

sh
certbot certonly --standalone -d kutup.example.com
cp /etc/letsencrypt/live/kutup.example.com/fullchain.pem nginx/certs/
cp /etc/letsencrypt/live/kutup.example.com/privkey.pem  nginx/certs/
docker compose exec nginx nginx -s reload

Operating your instance

Backups

Dump PostgreSQL and archive the SeaweedFS data dirs. The file chunks are ciphertext only — a stolen backup is useless without user keys.

Updating

git pull then docker compose up -d --build. Database migrations run automatically on backend startup.

Reverse proxy

Already running Nginx or Caddy? Bind the stack to 127.0.0.1:8080 and proxy to it (disable request buffering for large uploads).

Hardening

Change every secret, expose only the public TLS edge, and generate JWT_SECRET with openssl rand -hex 64. Keep the backend unreachable except through Nginx and protect the break-glass admin.

Office editing works in the default Compose build without DocumentServer or manual installation. For durable SeaweedFS metadata, retention, quotas, federation, and backup/restore requirements, see the documentation ↗.

Already running Kutup?

List your server in the public instance directory so others can find it.