Docs

Deployment

Deploy the full Grovs stack with Docker Compose — DNS, secrets, the one-command bring-up, and your first login.

The grovs-io/self-host repository brings up the entire platform with Docker Compose. This guide takes you from DNS to a working dashboard.

Prerequisites

  • A Linux server with Docker and Docker Compose v2, ports 80/443 open.
  • Two registrable domains (see below), with DNS you can edit.
  • A comfortable floor is 4 vCPU / 8 GB RAM / 80 GB SSD (e.g. a Hetzner CX33-class box).

DNS and domains

Grovs serves production and test links from per-project subdomains, and the backend tells them apart by their registrable domain. So you need two separate registrable domains — one for production, one for test — exactly like the managed cloud uses sqd.link (prod) and test-sqd.link (test).

The test domain must NOT be a sub-label of the production domain. test-links.example.com (a subdomain of example.com) will not route — the host parser splits proj.test-links.example.com into subdomain proj.test-links + domain example.com, so the test project never matches. Use a distinct registrable domain such as example-test.com.

Create every record below as an A record pointing at your server's IPv4 (add a matching AAAA for IPv6 if your host has one). Replace example.com / example-test.com with your own two domains.

Production domain (DOMAIN_LIVE, e.g. example.com):

TypeHostServes
AdashboardDashboard UI
AapiDashboard API + asset blobs
AsdkMobile / server SDKs (the SDK baseURL)
AmcpMCP OAuth/API
AgoShort-link helper
AlinksProduction links
ApreviewLink previews
A* (wildcard)Per-project production link subdomains

Test domain (DOMAIN_TEST, a separate registrable domain, e.g. example-test.com):

TypeHostServes
AlinksTest links
A* (wildcard)Per-project test link subdomains

The * wildcard on each domain is mandatory — every project gets its own random link subdomain (e.g. a1b2c3d4.example.com). Without it those subdomains 404 and can't obtain a TLS certificate.

The test domain only carries the test links — the dashboard, API, and SDK are shared (the SDK uses the same sdk. host with useTestEnvironment to pick the environment).

Deploy the stack

1

Clone with submodules

Bash
git clone --recursive https://github.com/grovs-io/self-host.git grovs-self-hosted
cd grovs-self-hosted
# if you forgot --recursive:
git submodule update --init --recursive
2

Generate secrets

The bundled script writes a .env with strong random secrets and a deterministic OAuth pair, and prints your bootstrap admin password:

Bash
./scripts/setup.sh

setup.sh keeps the must-match pairs in sync for you — the DATABASE_URL password, the MinIO ⇄ S3 credentials, and NEXT_PUBLIC_CLIENT_ID / CLIENT_SECRET ⇄ the OAuth pair. Save the admin password it prints.

3

Set your domains

Edit .env and fill in your hosts and the two domains:

Bash
DOMAIN_LIVE=example.com          # production registrable domain
DOMAIN_TEST=example-test.com     # SEPARATE registrable domain for test
 
DASHBOARD_HOST=dashboard.example.com
API_HOST=api.example.com
SDK_HOST=sdk.example.com
MCP_HOST=mcp.example.com
GO_HOST=go.example.com
LINKS_PROD_HOST=links.example.com
LINKS_TEST_HOST=links.example-test.com
PREVIEW_HOST=preview.example.com
 
ACME_EMAIL=[email protected]           # Let's Encrypt notices
BOOTSTRAP_ADMIN_EMAIL=[email protected]
 
# These must all point at your API + dashboard hosts:
SERVER_HOST=api.example.com
REACT_HOST=dashboard.example.com
S3_ASSET_PREFIX=https://api.example.com
NEXT_PUBLIC_API_URL=https://api.example.com

See Configuration for the full reference.

The NEXT_PUBLIC_* values are baked into the dashboard image at build time, so set them before the first build (changing them later requires a rebuild).

4

Build and start

Bash
docker compose --profile standalone build
docker compose --profile standalone up -d

On first start, a one-shot backend-migrate container runs the database migrations and seed (it creates the OAuth app and your bootstrap admin), then the web, worker, and dashboard services come up. The Caddy proxy obtains TLS certificates automatically.

First login

Open https://<DASHBOARD_HOST> and log in with BOOTSTRAP_ADMIN_EMAIL and the password setup.sh printed. No SMTP or SSO is required for the bootstrap admin.

Self-hosted instances disable public sign-ups. You log in as the bootstrap admin, and on first login the dashboard prompts you to create your first project (there is no pre-created default project). Invite additional members from the dashboard — each invite produces a copyable link (no email needed).

After creating a project, grab its API key (per environment — test vs production) for the SDKs.

Verify

Bash
# Backend health
curl -sS https://<API_HOST>/up            # -> 200
 
# Bootstrap-admin login returns an access token
curl -s -X POST https://<API_HOST>/oauth/token \
  -d grant_type=password \
  -d email="<BOOTSTRAP_ADMIN_EMAIL>" -d password="<ADMIN_PASSWORD>" \
  -d client_id="<OAUTH_CLIENT_UID>" -d client_secret="<OAUTH_CLIENT_SECRET>"

Upgrades

Bash
git submodule update --remote --merge        # pull latest backend + dashboard
docker compose --profile standalone build
docker compose run --rm backend-migrate      # migrate BEFORE restarting web/workers
docker compose --profile standalone up -d

Backups

Durable state lives in named Docker volumes — back these up off-box:

  • pg_data — PostgreSQL, the system of record (pg_dump / volume snapshots).
  • minio_data — uploaded assets and exports (mc mirror / snapshot).
  • redis_data — AOF; only undrained events are at risk.

Next step

Configuration — environment variables, branding, and email →