Configuration
Every environment variable for a self-hosted Grovs deployment, plus optional features, branding, and email.
Everything is configured through .env in the install directory. ./scripts/setup.sh writes it for you from the domains you enter: every hostname, every secret, and the OAuth pair. You normally edit it only to turn on optional features, rebrand, or tune capacity. After any change, apply it with docker compose --profile standalone up -d.
Version and hostnames
| Variable | What it does |
|---|---|
GROVS_VERSION | Shared tag for ghcr.io/grovs-io/backend and ghcr.io/grovs-io/dashboard. Use 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 | Hostname of the dashboard UI. |
API_HOST | Dashboard / 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 host 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, likewise. |
ACME_EMAIL | Email Let's Encrypt uses for certificate notices (standalone proxy only). |
The *_HOST variables are read by the Caddy proxy and the dashboard container. The backend derives its hosts from the domain variables below, so keep both sets consistent; setup.sh does.
Domains
| Variable | What it does |
|---|---|
SERVER_HOST_PROTOCOL / SERVER_HOST | Protocol + the 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 + 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>; they are served through the backend. |
Self-hosted flags
| Variable | What it does |
|---|---|
GROVS_SELF_HOSTED | Set to true by the stack. Disables billing and public sign-ups, 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 — are gated by GROVS_EE=true and an Enterprise build that includes the ee/ directory, so 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 them 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.
| 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
Generated by setup.sh. 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 | Credentials of the dashboard's OAuth application. The seed upserts the application to them and the dashboard container reads the same pair at start; nothing is baked into the dashboard image. |
PUBLIC_GO_PROJECT_IDENTIFIER | Identifier of the quick-link project used by the managed cloud. It is not seeded on self-hosted deployments, so the value is unused. |
RAILS_ENV, RAILS_SERVE_STATIC_FILES, and logging to stdout are fixed by the image and Compose file. 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 with no SMTP or SSO. After login you're prompted to create your first project. |
Optional features
Off by default. Each is a few lines in .env followed by 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_HOSTThen, per domain: the customer creates CNAME links.customer.com → <SELF_HOSTED_INGRESS_HOST>, an admin adds the domain on the project's Domain page, and 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 flips DNS. See Custom Domains.
Migrating links from Branch or AppsFlyer
MIGRATIONS_ENABLED=trueThen set the migration up on the project's Domain page with the provider credentials. Custom domains must be enabled too when the old links live on a domain you will point at Grovs. 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, fill in the SMTP block and set the delivery method:
| Variable | What it does |
|---|---|
MAILER_DELIVERY_METHOD | Set to 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, 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 pull images from two places: per-project and per-link images you set in the dashboard, and fallback defaults for when a link has none.
Default images
If a project has no app-store icon and a link has no custom preview image, Grovs falls back to these. They ship pointing 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 / 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. Point them at 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 per-link preview images you upload in the dashboard are stored in the storage volume (or S3) and served through the API host. For them to appear, S3_ASSET_PREFIX must point at https://<API_HOST> and that host must be publicly reachable over HTTPS. A wrong S3_ASSET_PREFIX is the usual cause of "uploaded image doesn't show".
Facebook, LinkedIn, iMessage, and others cache OG data aggressively. If a link you already shared still looks blank after you fix the image, re-scrape it (for example with Facebook's Sharing Debugger) or test with a fresh link.
Local trial variables
setup.sh writes these only for the lvh.me trial: GROVS_LOCAL=true, GROVS_WEB_PORT (default 80), and GROVS_DASHBOARD_PORT (default 3002), with http:// protocols. Set the two ports before running the installer to change them.