Docs

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

VariableWhat it does
GROVS_VERSIONShared 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_NAMECompose project name, grovs by default.
DASHBOARD_HOSTHostname of the dashboard UI.
API_HOSTDashboard / REST API host. Also serves uploaded assets and the /up health check.
SDK_HOSTMobile, web, and server SDK host — this exact value is the SDK baseURL.
MCP_HOSTMCP (Model Context Protocol) OAuth and API host.
GO_HOSTReserved host for the quick-link helper. Not enabled on self-hosted deployments.
PREVIEW_HOSTLink preview pages.
LINKS_PROD_HOSTProduction links host under DOMAIN_LIVE, given a pre-issued certificate by the proxy.
LINKS_TEST_HOSTTest links host under DOMAIN_TEST, likewise.
ACME_EMAILEmail 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

VariableWhat it does
SERVER_HOST_PROTOCOL / SERVER_HOSTProtocol + 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_HOSTProtocol + host for dashboard links, for example in emails — your dashboard host.
DOMAIN_LIVELinks domain. Production project links are <project>.<DOMAIN_LIVE>. Defaults to the app domain; a branded short domain such as acme.link also works.
DOMAIN_TESTTest links domain. test.<DOMAIN_LIVE> by default; a separate domain also works.
PREVIEW_BASE_URLFull URL of the preview host. Required for the preview page and copy to clipboard.
MCP_CONSENT_URLOAuth consent URL for MCP — your dashboard's /mcp/authorize.
S3_ASSET_PREFIXPublic URL prefix for uploads — https://<API_HOST>; they are served through the backend.

Self-hosted flags

VariableWhat it does
GROVS_SELF_HOSTEDSet 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

VariableWhat it does
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DBCredentials and name for the bundled PostgreSQL. Compose builds the connection string from these.
POSTGRES_MAX_CONNECTIONSServer-side connection ceiling. Keep it at or above WEB_CONCURRENCY × RAILS_DB_POOL plus the worker pools.
CLICKHOUSE_PASSWORDPassword 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_WRITESClickHouse is the event store and serves every analytics read; PostgreSQL keeps a spill fallback only. Leave them as shipped.
DASHBOARD_CACHE_TTL_SECONDSCache 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.

VariableWhat it does
WEB_CONCURRENCYPuma worker processes in the web container.
RAILS_MAX_THREADSThreads per Puma worker.
RAILS_DB_POOLDatabase connection pool per process.
SIDEKIQ_EVENTS_CONCURRENCYThreads for the events worker.

Uploads

VariableWhat it does
ACTIVE_STORAGE_SERVICElocal (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_BUCKETS3 credentials, region, and bucket.
S3_ENDPOINT / S3_FORCE_PATH_STYLEOnly 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.

VariableWhat it does
SECRET_KEY_BASERails session and signing secret.
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY, _DETERMINISTIC_KEY, _KEY_DERIVATION_SALTAt-rest encryption keys. Rotating them makes encrypted columns unreadable.
ADMIN_API_KEY / DIAGNOSTICS_API_KEY / SENT_QUOTAS_WEBHOOK_KEYKeys guarding internal admin, diagnostics, and webhook endpoints.
OAUTH_CLIENT_UID / OAUTH_CLIENT_SECRETCredentials 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_IDENTIFIERIdentifier 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

VariableWhat it does
BOOTSTRAP_ADMIN_EMAIL / BOOTSTRAP_ADMIN_PASSWORDThe 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>:

Bash
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_HOST

Then, 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.

Bash
MIGRATIONS_ENABLED=true

Then 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

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:

VariableWhat it does
MAILER_DELIVERY_METHODSet 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_DOMAINSMTP server address, port, HELO domain.
SMTP_USERNAME / SMTP_PASSWORDSMTP authentication.
SMTP_AUTHENTICATIONplain, login, or cram_md5.
SMTP_ENABLE_STARTTLS_AUTOtrue to use STARTTLS when available.
MAILER_FROMFrom address for outgoing email.

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:

VariableUsed for
DEFAULT_LOGO_URLApp icon on the link landing page when a project has no app-store icon.
DEFAULT_SOCIAL_PREVIEW_URLOG / Twitter card image when a link has no custom preview image.
DEFAULT_LINK_TITLEDefault og:title for link previews.
DEFAULT_LINK_SUBTITLEDefault 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.

Next step

Point the SDKs at your self-hosted backend →

Edit this page on GitHubLast updated 2026-09-16