Configuration
Every environment variable for a self-hosted installation, plus optional features, email and branding.
All settings live in .env in the install directory. ./scripts/setup.sh writes it from the domains you enter: every hostname, every secret and the OAuth pair. You edit it only to turn on optional features, rebrand or tune capacity. Apply changes with:
docker compose --profile standalone up -dVersion and hostnames
| Variable | What it does |
|---|---|
GROVS_VERSION | Shared tag for ghcr.io/grovs-io/backend and ghcr.io/grovs-io/dashboard. Run the latest release lookup before setup to select a numbered version. Existing Compose installations can use latest to follow new images on each pull and upgrade. |
COMPOSE_PROJECT_NAME | Compose project name, grovs by default. |
DASHBOARD_HOST | Dashboard UI host. |
API_HOST | Dashboard and REST API host. Also serves uploaded assets and the /up health check. |
SDK_HOST | Mobile, web and server SDK host. This exact value is the SDK baseURL. |
MCP_HOST | MCP (Model Context Protocol) OAuth and API host. |
GO_HOST | Reserved for the quick-link helper. Not enabled on self-hosted deployments. |
PREVIEW_HOST | Link preview pages. |
LINKS_PROD_HOST | Production links host under DOMAIN_LIVE, given a pre-issued certificate by the proxy. |
LINKS_TEST_HOST | Test links host under DOMAIN_TEST, given a pre-issued certificate by the proxy. |
ACME_EMAIL | Email Let's Encrypt uses for certificate notices. Standalone proxy only. |
The Caddy proxy and the dashboard container read the *_HOST variables. The backend derives its hosts from the domain variables below. Keep both sets consistent; setup.sh does.
Domains
| Variable | What it does |
|---|---|
SERVER_HOST_PROTOCOL / SERVER_HOST | Protocol and bare app domain (acme.com). The backend derives api., sdk., go., mcp. and preview. from it. Never set it to the api. host: the backend refuses to boot when it starts with a reserved label. |
REACT_HOST_PROTOCOL / REACT_HOST | Protocol and host for dashboard links, for example in emails. Your dashboard host. |
DOMAIN_LIVE | Links domain. Production project links are <project>.<DOMAIN_LIVE>. Defaults to the app domain. A branded short domain such as acme.link also works. |
DOMAIN_TEST | Test links domain. test.<DOMAIN_LIVE> by default. A separate domain also works. |
PREVIEW_BASE_URL | Full URL of the preview host. Required for the preview page and copy to clipboard. |
MCP_CONSENT_URL | OAuth consent URL for MCP: your dashboard's /mcp/authorize. |
S3_ASSET_PREFIX | Public URL prefix for uploads: https://<API_HOST>. Uploads are served through the backend. |
Self-hosted mode
| Variable | What it does |
|---|---|
GROVS_SELF_HOSTED | Set to true by the stack. Disables billing and public sign-up, removes usage quotas, turns member invites into copyable links when there is no SMTP, and serves uploads through the API host. |
The published backend image is the Community edition. Enterprise features (revenue tracking, the audit log, single sign-on and SCIM) need GROVS_EE=true and an Enterprise build that includes the ee/ directory. The flag alone does nothing on this stack. Contact Grovs for an Enterprise image.
Database, cache and analytics store
| Variable | What it does |
|---|---|
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB | Credentials and name for the bundled PostgreSQL. Compose builds the connection string from these. |
POSTGRES_MAX_CONNECTIONS | Server-side connection ceiling. Keep it at or above WEB_CONCURRENCY × RAILS_DB_POOL plus the worker pools. |
CLICKHOUSE_PASSWORD | Password of the grovs ClickHouse user, generated by setup.sh. |
CLICKHOUSE_WRITE_ENABLED, CLICKHOUSE_READ_ENABLED, CLICKHOUSE_PRIMARY, CLICKHOUSE_ANALYTICS_ROLLUPS_READ_ENABLED, CLICKHOUSE_ATTRIBUTION_READ_ENABLED, CLICKHOUSE_LINK_DIMENSIONS_READ_ENABLED, CLICKHOUSE_ROLLUP_FAST_LANE, REVENUE_READS_FROM_LEDGER, PG_SHADOW_WRITES | ClickHouse is the event store and serves every analytics read. PostgreSQL keeps a spill fallback only. Leave these as shipped. |
DASHBOARD_CACHE_TTL_SECONDS | Cache lifetime for dashboard analytics reads. 60 by default. |
DATABASE_URL, REDIS_URL and CLICKHOUSE_URL are not in .env; Compose sets them for the containers. For a custom deployment on managed services, set them on the backend containers directly.
Process sizing
Raise these as you add CPU and RAM. Sizing lists the shipped values, the connection budget and what to raise first.
| Variable | What it does |
|---|---|
WEB_CONCURRENCY | Puma worker processes in the web container. |
RAILS_MAX_THREADS | Threads per Puma worker. |
RAILS_DB_POOL | Database connection pool per process. |
SIDEKIQ_EVENTS_CONCURRENCY | Threads for the events worker. |
Uploads
| Variable | What it does |
|---|---|
ACTIVE_STORAGE_SERVICE | local (default) keeps uploads in the storage volume. amazon uses S3 with the variables below. |
AWS_S3_KEY_ID / AWS_S3_ACCESS_KEY / AWS_S3_REGION / AWS_S3_BUCKET | S3 credentials, region and bucket. |
S3_ENDPOINT / S3_FORCE_PATH_STYLE | Only for S3-compatible stores you run yourself (MinIO and similar): the endpoint URL and true. Leave empty for AWS S3. |
Secrets and keys
setup.sh generates these. Never change them after the first start.
| Variable | What it does |
|---|---|
SECRET_KEY_BASE | Rails session and signing secret. |
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY, _DETERMINISTIC_KEY, _KEY_DERIVATION_SALT | At-rest encryption keys. Rotating them makes encrypted columns unreadable. |
ADMIN_API_KEY / DIAGNOSTICS_API_KEY / SENT_QUOTAS_WEBHOOK_KEY | Keys guarding internal admin, diagnostics and webhook endpoints. |
OAUTH_CLIENT_UID / OAUTH_CLIENT_SECRET | The dashboard's OAuth application. The seed upserts the application to this pair and the dashboard container reads it at start. Nothing is baked into the dashboard image. |
PUBLIC_GO_PROJECT_IDENTIFIER | Quick-link project used by the managed cloud. Not seeded on self-hosted deployments, so the value is unused. |
The image and Compose file fix RAILS_ENV, RAILS_SERVE_STATIC_FILES and logging to stdout. To change log verbosity, set RAILS_LOG_LEVEL on the backend containers. The default is error.
First-run admin
| Variable | What it does |
|---|---|
BOOTSTRAP_ADMIN_EMAIL / BOOTSTRAP_ADMIN_PASSWORD | The seed creates this admin so you can log in without SMTP or SSO. After login you are asked to create your first project. |
Local trial
setup.sh writes these only for the lvh.me trial, with http:// protocols:
| Variable | Default |
|---|---|
GROVS_LOCAL | true |
GROVS_WEB_PORT | 80 |
GROVS_DASHBOARD_PORT | 3002 |
Set the two ports before running the installer to change them.
Optional features
Each is off by default. Add the lines to .env, then run docker compose --profile standalone up -d.
Custom domains
Let a project serve links on a domain your customer owns (links.customer.com) instead of <project>.<links domain>:
CUSTOM_DOMAINS_ENABLED=true
CUSTOM_DOMAINS_PROVIDER=manual
SELF_HOSTED_INGRESS_HOST=links.acme.link # the host customers point their CNAME at; defaults to SERVER_HOSTFor each domain:
- The customer creates
CNAME links.customer.com → <SELF_HOSTED_INGRESS_HOST>. - An admin adds the domain on the project's Domain page.
- Grovs verifies it within a minute by probing
https://links.customer.com/.well-known/grovs-domain-verification.
The bundled proxy issues the certificate on that first request. Behind your own proxy, attach a certificate for the host before the customer switches DNS. See Custom domains.
Link migration from Branch or AppsFlyer
MIGRATIONS_ENABLED=trueThen set up the migration on the project's Domain page with the provider credentials. If the old links live on a domain you will point at Grovs, enable custom domains too. See Migration guides.
Email is off by default. The bootstrap admin needs none, and member invites produce a copyable link. To send password-reset, invite and data-export emails, set:
| Variable | What it does |
|---|---|
MAILER_DELIVERY_METHOD | smtp to send mail. Leave empty to run without email: invites become copyable links, and password resets and exports cannot be delivered. |
SMTP_ADDRESS / SMTP_PORT / SMTP_DOMAIN | SMTP server address, port and HELO domain. |
SMTP_USERNAME / SMTP_PASSWORD | SMTP authentication. |
SMTP_AUTHENTICATION | plain, login or cram_md5. |
SMTP_ENABLE_STARTTLS_AUTO | true to use STARTTLS when available. |
MAILER_FROM | From address for outgoing email. |
Branding and link images
Link landing pages and social previews use the per-project and per-link images you set in the dashboard. When those are missing, they fall back to the defaults below.
Default images
The defaults point at the Grovs assets, so links are never blank out of the box. Set your own URLs to rebrand.
| Variable | Used for |
|---|---|
DEFAULT_LOGO_URL | App icon on the link landing page when a project has no app-store icon. |
DEFAULT_SOCIAL_PREVIEW_URL | OG and Twitter card image when a link has no custom preview image. |
DEFAULT_LINK_TITLE | Default og:title for link previews. |
DEFAULT_LINK_SUBTITLE | Default og:description for link previews. |
Do not blank these. With no value, link pages show an empty icon and social shares render an empty card. Use publicly reachable image URLs. A 1200×630 JPG or PNG works well for the social preview. A per-link image set in the dashboard overrides the defaults.
Uploaded images must be reachable
App icons and preview images you upload are stored in the storage volume (or S3) and served through the API host. For them to appear, S3_ASSET_PREFIX must be https://<API_HOST> and that host must be publicly reachable over HTTPS. A wrong S3_ASSET_PREFIX is the usual cause of an uploaded image not showing.
Facebook, LinkedIn, iMessage and others cache OG data aggressively. If a shared link still looks blank after you fix the image, re-scrape it (for example with Facebook's Sharing Debugger) or test with a fresh link.