---
title: "Deploy Grovs Community"
description: "Self-host deep links, mobile attribution, referrals and analytics."
category: "Analytics"
url: https://railway.com/deploy/grovs-community
---

# Deploy Grovs Community

Self-host deep links, mobile attribution, referrals and analytics.

**[Deploy Grovs Community on Railway](https://railway.com/template/grovs-community)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/grovs-community/manifest.json

- **Creator:** Dragos Dobrean's Projects
- **Category:** Analytics

## Template content

### dashboard https://docs.grovs.io/logo.svg

- **Image:** ghcr.io/grovs-io/dashboard:2.3.1
- **Health check:** /login
- **Public domain:** Yes

### redis https://devicons.railway.app/i/redis.svg

- **Image:** redis:7-alpine
- **Start command:** `sh -ec 'exec redis-server --appendonly yes --maxmemory-policy noeviction --requirepass "$REDIS_PASSWORD"'`

### worker-1 https://docs.grovs.io/logo.svg

- **Image:** ghcr.io/grovs-io/backend:2.3.1
- **Start command:** `bash -ec '#!/usr/bin/env bash
# Sourced into the commands embedded in Railway/Render configuration.
# No files from this repository need to be present in the application image.
set -eu
: "${SERVER_HOST:?Set your app domain}"
: "${DOMAIN_LIVE:?Set your production links domain}"
: "${DOMAIN_TEST:?Set your test links domain}"
: "${AWS_S3_KEY_ID:?Set object storage credentials}"
: "${AWS_S3_ACCESS_KEY:?Set object storage credentials}"
: "${AWS_S3_REGION:?Set your bucket region}"
: "${AWS_S3_BUCKET:?Set your bucket name}"
export ACTIVE_STORAGE_SERVICE=amazon
export SERVER_HOST_PROTOCOL=https:// REACT_HOST_PROTOCOL=https://
export REACT_HOST="dashboard.$SERVER_HOST"
export API_HOST="api.$SERVER_HOST" SDK_HOST="sdk.$SERVER_HOST"
export DASHBOARD_HOST="$REACT_HOST" MCP_HOST="mcp.$SERVER_HOST" GO_HOST="go.$SERVER_HOST"
export PREVIEW_HOST="preview.$SERVER_HOST"
export LINKS_PROD_HOST="links.$DOMAIN_LIVE" LINKS_TEST_HOST="links.$DOMAIN_TEST"
export PREVIEW_BASE_URL="https://preview.$SERVER_HOST"
export MCP_CONSENT_URL="https://dashboard.$SERVER_HOST/mcp/authorize"
export S3_ASSET_PREFIX="https://api.$SERVER_HOST"
encode_password() {
  ruby -ruri -e '"'"'print URI.encode_www_form_component(ENV.fetch(ARGV.fetch(0))).gsub("+", "%20")'"'"' "$1"
}
if [ -z "${DATABASE_URL:-}" ]; then
  : "${POSTGRES_HOST:?Set the private PostgreSQL host}"
  DATABASE_URL="postgres://grovs:$(encode_password POSTGRES_PASSWORD)@$POSTGRES_HOST:5432/grovs_production"
  export DATABASE_URL
fi
if [ -z "${REDIS_URL:-}" ]; then
  : "${REDIS_HOST:?Set the private Redis host}"
  REDIS_URL="redis://:$(encode_password REDIS_PASSWORD)@$REDIS_HOST:6379/0"
  export REDIS_URL
fi
if [ -z "${CLICKHOUSE_URL:-}" ]; then
  : "${CLICKHOUSE_HOST:?Set the private ClickHouse host}"
  : "${CLICKHOUSE_PASSWORD:?Set the ClickHouse password}"
  ENCODED_PASSWORD=$(encode_password CLICKHOUSE_PASSWORD)
  export CLICKHOUSE_URL="http://grovs:$ENCODED_PASSWORD@$CLICKHOUSE_HOST:8123"
fi

wait_for_http() {
  local attempt
  for ((attempt=0; attempt<120; attempt++)); do
    if curl --fail --silent --max-time 5 "$1" >/dev/null; then return 0; fi
    sleep 5
  done
  echo "Dependency did not become healthy within the startup window." >&2
  return 1
}

wait_for_http "http://$WEB_HOST:${WEB_PORT:-3000}/up"
pids=()
trap '"'"'kill "${pids[@]}" 2>/dev/null || true'"'"' EXIT
trap '"'"'exit 143'"'"' TERM INT
bundle exec sidekiq -C config/sidekiq_scheduler.yml &
pids+=($!)
bundle exec sidekiq -C config/sidekiq_worker.yml &
pids+=($!)
bundle exec sidekiq -C config/sidekiq_batch.yml &
pids+=($!)
wait -n
'`

### web https://docs.grovs.io/logo.svg

- **Image:** ghcr.io/grovs-io/backend:2.3.1
- **Start command:** `bash -ec '#!/usr/bin/env bash
# Sourced into the commands embedded in Railway/Render configuration.
# No files from this repository need to be present in the application image.
set -eu
: "${SERVER_HOST:?Set your app domain}"
: "${DOMAIN_LIVE:?Set your production links domain}"
: "${DOMAIN_TEST:?Set your test links domain}"
: "${AWS_S3_KEY_ID:?Set object storage credentials}"
: "${AWS_S3_ACCESS_KEY:?Set object storage credentials}"
: "${AWS_S3_REGION:?Set your bucket region}"
: "${AWS_S3_BUCKET:?Set your bucket name}"
export ACTIVE_STORAGE_SERVICE=amazon
export SERVER_HOST_PROTOCOL=https:// REACT_HOST_PROTOCOL=https://
export REACT_HOST="dashboard.$SERVER_HOST"
export API_HOST="api.$SERVER_HOST" SDK_HOST="sdk.$SERVER_HOST"
export DASHBOARD_HOST="$REACT_HOST" MCP_HOST="mcp.$SERVER_HOST" GO_HOST="go.$SERVER_HOST"
export PREVIEW_HOST="preview.$SERVER_HOST"
export LINKS_PROD_HOST="links.$DOMAIN_LIVE" LINKS_TEST_HOST="links.$DOMAIN_TEST"
export PREVIEW_BASE_URL="https://preview.$SERVER_HOST"
export MCP_CONSENT_URL="https://dashboard.$SERVER_HOST/mcp/authorize"
export S3_ASSET_PREFIX="https://api.$SERVER_HOST"
encode_password() {
  ruby -ruri -e '"'"'print URI.encode_www_form_component(ENV.fetch(ARGV.fetch(0))).gsub("+", "%20")'"'"' "$1"
}
if [ -z "${DATABASE_URL:-}" ]; then
  : "${POSTGRES_HOST:?Set the private PostgreSQL host}"
  DATABASE_URL="postgres://grovs:$(encode_password POSTGRES_PASSWORD)@$POSTGRES_HOST:5432/grovs_production"
  export DATABASE_URL
fi
if [ -z "${REDIS_URL:-}" ]; then
  : "${REDIS_HOST:?Set the private Redis host}"
  REDIS_URL="redis://:$(encode_password REDIS_PASSWORD)@$REDIS_HOST:6379/0"
  export REDIS_URL
fi
if [ -z "${CLICKHOUSE_URL:-}" ]; then
  : "${CLICKHOUSE_HOST:?Set the private ClickHouse host}"
  : "${CLICKHOUSE_PASSWORD:?Set the ClickHouse password}"
  ENCODED_PASSWORD=$(encode_password CLICKHOUSE_PASSWORD)
  export CLICKHOUSE_URL="http://grovs:$ENCODED_PASSWORD@$CLICKHOUSE_HOST:8123"
fi

wait_for_http() {
  local attempt
  for ((attempt=0; attempt<120; attempt++)); do
    if curl --fail --silent --max-time 5 "$1" >/dev/null; then return 0; fi
    sleep 5
  done
  echo "Dependency did not become healthy within the startup window." >&2
  return 1
}

exec bundle exec puma -b "tcp://[::]:${PORT:-3000}"'`
- **Health check:** /up
- **Public domain:** Yes

### postgres https://devicons.railway.app/i/postgresql.svg

- **Image:** postgres:16-alpine

### clickhouse https://devicons.railway.app/i/clickhouse.svg

- **Image:** clickhouse/clickhouse-server:25.3

### worker-2 https://docs.grovs.io/logo.svg

- **Image:** ghcr.io/grovs-io/backend:2.3.1
- **Start command:** `bash -ec '#!/usr/bin/env bash
# Sourced into the commands embedded in Railway/Render configuration.
# No files from this repository need to be present in the application image.
set -eu
: "${SERVER_HOST:?Set your app domain}"
: "${DOMAIN_LIVE:?Set your production links domain}"
: "${DOMAIN_TEST:?Set your test links domain}"
: "${AWS_S3_KEY_ID:?Set object storage credentials}"
: "${AWS_S3_ACCESS_KEY:?Set object storage credentials}"
: "${AWS_S3_REGION:?Set your bucket region}"
: "${AWS_S3_BUCKET:?Set your bucket name}"
export ACTIVE_STORAGE_SERVICE=amazon
export SERVER_HOST_PROTOCOL=https:// REACT_HOST_PROTOCOL=https://
export REACT_HOST="dashboard.$SERVER_HOST"
export API_HOST="api.$SERVER_HOST" SDK_HOST="sdk.$SERVER_HOST"
export DASHBOARD_HOST="$REACT_HOST" MCP_HOST="mcp.$SERVER_HOST" GO_HOST="go.$SERVER_HOST"
export PREVIEW_HOST="preview.$SERVER_HOST"
export LINKS_PROD_HOST="links.$DOMAIN_LIVE" LINKS_TEST_HOST="links.$DOMAIN_TEST"
export PREVIEW_BASE_URL="https://preview.$SERVER_HOST"
export MCP_CONSENT_URL="https://dashboard.$SERVER_HOST/mcp/authorize"
export S3_ASSET_PREFIX="https://api.$SERVER_HOST"
encode_password() {
  ruby -ruri -e '"'"'print URI.encode_www_form_component(ENV.fetch(ARGV.fetch(0))).gsub("+", "%20")'"'"' "$1"
}
if [ -z "${DATABASE_URL:-}" ]; then
  : "${POSTGRES_HOST:?Set the private PostgreSQL host}"
  DATABASE_URL="postgres://grovs:$(encode_password POSTGRES_PASSWORD)@$POSTGRES_HOST:5432/grovs_production"
  export DATABASE_URL
fi
if [ -z "${REDIS_URL:-}" ]; then
  : "${REDIS_HOST:?Set the private Redis host}"
  REDIS_URL="redis://:$(encode_password REDIS_PASSWORD)@$REDIS_HOST:6379/0"
  export REDIS_URL
fi
if [ -z "${CLICKHOUSE_URL:-}" ]; then
  : "${CLICKHOUSE_HOST:?Set the private ClickHouse host}"
  : "${CLICKHOUSE_PASSWORD:?Set the ClickHouse password}"
  ENCODED_PASSWORD=$(encode_password CLICKHOUSE_PASSWORD)
  export CLICKHOUSE_URL="http://grovs:$ENCODED_PASSWORD@$CLICKHOUSE_HOST:8123"
fi

wait_for_http() {
  local attempt
  for ((attempt=0; attempt<120; attempt++)); do
    if curl --fail --silent --max-time 5 "$1" >/dev/null; then return 0; fi
    sleep 5
  done
  echo "Dependency did not become healthy within the startup window." >&2
  return 1
}

wait_for_http "http://$WEB_HOST:${WEB_PORT:-3000}/up"
pids=()
trap '"'"'kill "${pids[@]}" 2>/dev/null || true'"'"' EXIT
trap '"'"'exit 143'"'"' TERM INT
bundle exec sidekiq -C config/sidekiq_maintenance.yml &
pids+=($!)
bundle exec sidekiq -C config/sidekiq_device_updates.yml &
pids+=($!)
wait -n
'`

## Documentation

# Deploy and Host Grovs Community on Railway

Self-host Grovs for deep links, mobile attribution, referrals and analytics using
the official Community images. Your deployment includes the dashboard, API, two
workers, PostgreSQL, Redis and ClickHouse with persistent database volumes.

## About Hosting Grovs Community

The services run from versioned container images. PostgreSQL stores application
data, ClickHouse stores analytics and Redis supports background jobs. The API
initializes schemas before starting, and workers wait for its health endpoint.

## Why Deploy Grovs Community on Railway

Railway brings the application and database services into one project with
private networking, persistent volumes, deployment logs and HTTPS endpoints.
This template wires the services together and generates their shared secrets.

## Common Use Cases

- Deep links that open the right screen in an installed app.
- Attribution for mobile campaigns and referrals.
- Analytics for links and app events under your own account.

## Dependencies for Grovs Community

- Use a Railway plan that supports seven services, sufficient RAM/storage and
  the custom domains below. Compute, volumes and network usage are billed by Railway.
- Bring a private S3-compatible bucket and bucket-scoped credentials for uploads.
- Choose an app base domain and production/test link base domains you control.

For example, set `SERVER_HOST=grovs.example.com`,
`DOMAIN_LIVE=links.example.com` and `DOMAIN_TEST=test.links.example.com`.
Enter these without a protocol, wildcard or path. Set your admin email and the
four required S3 variables on `web`. Railway generates separate secrets for each
deployment and shares them with the workers and dashboard through references.

## Finish setup

1. Keep all services in one Railway region. Size database volumes for your data
   and retention, especially ClickHouse. Keep both workers always running.
2. The API pre-deploy command initializes the database schemas and admin login.
   If databases are still starting, wait for them to be ready and redeploy `web`.
3. In Railway Networking, add these custom domains, all targeting port **3000**:
   - `dashboard`: `dashboard.grovs.example.com`
   - `web`: `api.grovs.example.com`, `sdk.grovs.example.com`,
     `mcp.grovs.example.com`, `go.grovs.example.com`, `preview.grovs.example.com`,
     `*.links.example.com` and `*.test.links.example.com`
4. Add Railway's DNS and certificate-verification records at your DNS provider.
   The generated Railway domains are useful for health checks; use your custom
   domains for Grovs login and links.
5. Sign in at `https://dashboard.grovs.example.com` using `BOOTSTRAP_ADMIN_EMAIL`
   and the generated `BOOTSTRAP_ADMIN_PASSWORD` in `web`'s variables.
6. Check the API `/up` endpoint, create a project, open production and test links,
   verify analytics and upload an image.

## Updates

Back up PostgreSQL, ClickHouse, uploads and configuration before upgrading. Stop
both workers, update the API image to the desired published version and let its
migrations finish. Update the dashboard and both workers to the same version,
then restart them. Keep `worker-1` at **one instance**, including during rollouts,
because it owns the scheduler.

This template has configuration checks but has not yet been validated with a
live Railway deployment.

- [Full Railway guide](https://github.com/grovs-io/self-host/blob/main/docs/deploy/railway.md)
- [Source and deployment configuration](https://github.com/grovs-io/self-host)
- [Grovs](https://www.grovs.io)


## Similar templates

- [Typesense vs Meilisearch](https://railway.com/deploy/typesense-vs-meilisearch) — self-hosted Typesense vs Meilisearch
- [Betterlytics](https://railway.com/deploy/betterlytics) — Betterlytics is a cookieless analytics platform GDPR-compliant.
- [Finance Tracker](https://railway.com/deploy/finance-tracker-1) — Private multi-user household finance ledger with budgets and CSV import.

Open this page in a browser: https://railway.com/deploy/grovs-community
