Cap
Beautiful, shareable screen recordings, open source and self-hosted
Alternative to: loom, cloudapp
Cap is the open source alternative to Loom, offering fast screen recording, polished local editing, instant share links, comments, and transcripts. It supports team workspaces, custom domains, and custom S3 storage, with full self-hosting for teams that want to own their data.
Cap Docker Compose example
Self-host Cap on your own server, homelab, or VPS starting from this Docker Compose example.
It runs Cap in Docker containers using the official ghcr.io/capsoftware/cap-web:latest, ghcr.io/capsoftware/cap-media-server:latest, mysql:8.0, minio/minio:latest, minio/mc:latest images, with persistent volumes and automatic restarts preconfigured.
Review the environment variables and adjust them to your setup, save the file as compose.yml (or docker-compose.yml), and start the stack with docker compose up -d.
services:
cap-web:
image: ghcr.io/capsoftware/cap-web:latest
restart: unless-stopped
depends_on:
mysql:
condition: service_healthy
minio:
condition: service_healthy
environment:
# Connection string for the MySQL database (see the mysql service below).
DATABASE_URL: "mysql://cap:cap-local-pwd-123@mysql:3306/cap"
# Public URL where users reach Cap.
WEB_URL: "http://localhost:3000"
# Auth base URL. Keep it the same as WEB_URL.
NEXTAUTH_URL: "http://localhost:3000"
# 64-character hex key used to encrypt sensitive database fields. Generate a unique one.
DATABASE_ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
# Secret used to sign authentication sessions. Generate a unique one.
NEXTAUTH_SECRET: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
# Shared secret used to authenticate media-server webhooks. Generate a unique one.
MEDIA_SERVER_WEBHOOK_SECRET: "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
# Access key for the S3-compatible object store (matches the MinIO root user below).
CAP_AWS_ACCESS_KEY: "cap-admin"
# Secret key for the S3-compatible object store (matches the MinIO root password below).
CAP_AWS_SECRET_KEY: "cap-minio-pwd-456"
# Bucket that stores recordings.
CAP_AWS_BUCKET: "cap"
# Region reported to the S3 client.
CAP_AWS_REGION: "us-east-1"
# Public URL browsers use to upload/download recordings directly from object storage.
S3_PUBLIC_ENDPOINT: "http://localhost:9000"
# Internal URL the app uses to reach object storage over the compose network.
S3_INTERNAL_ENDPOINT: "http://minio:9000"
# Use path-style S3 URLs (required for MinIO).
S3_PATH_STYLE: "true"
# Resend API key for transactional email (optional).
RESEND_API_KEY: ""
# Verified sender domain for Resend email (optional).
RESEND_FROM_DOMAIN: ""
# Google OAuth client ID for sign-in (optional).
GOOGLE_CLIENT_ID: ""
# Google OAuth client secret for sign-in (optional).
GOOGLE_CLIENT_SECRET: ""
# Apple OAuth client ID for sign-in (optional).
APPLE_CLIENT_ID: ""
# Apple OAuth client secret for sign-in (optional).
APPLE_CLIENT_SECRET: ""
# Internal URL the app uses to reach the media-server over the compose network.
MEDIA_SERVER_URL: "http://media-server:3456"
# Internal URL the media-server uses to call back into the web app.
MEDIA_SERVER_WEBHOOK_URL: "http://cap-web:3000"
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3000/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
media-server:
image: ghcr.io/capsoftware/cap-media-server:latest
restart: unless-stopped
environment:
# Port the media-server listens on inside the compose network.
PORT: "3456"
# Shared secret used to authenticate media-server webhooks. Generate a unique one.
MEDIA_SERVER_WEBHOOK_SECRET: "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
healthcheck:
test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3456/health').then((response) => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
mysql:
image: mysql:8.0
restart: unless-stopped
environment:
# Name of the MySQL database.
MYSQL_DATABASE: "cap"
# Name of the MySQL user.
MYSQL_USER: "cap"
# Password for the MySQL user. Fed into DATABASE_URL via overrides.
MYSQL_PASSWORD: "cap-local-pwd-123"
# Root password. Used only inside the compose network.
MYSQL_ROOT_PASSWORD: "cap-root-pwd-789"
command:
- --max_connections=1000
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
- cap-mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "cap", "-pcap-local-pwd-123"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
minio:
image: minio/minio:latest
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
# Root user / access key for the S3-compatible object store.
MINIO_ROOT_USER: "cap-admin"
# Root password / secret key for the S3-compatible object store.
MINIO_ROOT_PASSWORD: "cap-minio-pwd-456"
volumes:
- cap-minio-data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
minio-setup:
image: minio/mc:latest
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set capminio http://minio:9000 $${MINIO_ROOT_USER:-cap-admin} $${MINIO_ROOT_PASSWORD:-cap-minio-pwd-456};
mc mb capminio/cap --ignore-existing;
exit 0;
"
environment:
# Root user / access key for the S3-compatible object store.
MINIO_ROOT_USER: "cap-admin"
# Root password / secret key for the S3-compatible object store.
MINIO_ROOT_PASSWORD: "cap-minio-pwd-456"
volumes:
cap-mysql-data:
cap-minio-data: Prefer a managed setup? WinterFlow installs, configures, and updates Cap for you using this same Docker Compose configuration.