A template for self-hosting Grist with traefik and docker compose

Based on this example with Traefik, we are regularly using and updating Grist with this configuration:

  • compose.yml
networks:
  internal: {}
  web:
    external: true

services:

  grist:
    image: gristlabs/grist
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      - GRIST_DOMAIN
      - APP_HOME_URL

      - REDIS_URL

      - TYPEORM_TYPE
      - TYPEORM_HOST
      - TYPEORM_DATABASE
      - TYPEORM_USERNAME
      - TYPEORM_PASSWORD

#      - GRIST_DOCS_MINIO_ENDPOINT
#      - GRIST_DOCS_MINIO_USE_SSL
#      - GRIST_DOCS_MINIO_BUCKET_REGION
#      - GRIST_DOCS_MINIO_BUCKET
#      - GRIST_DOCS_MINIO_ACCESS_KEY
#      - GRIST_DOCS_MINIO_SECRET_KEY

      - GRIST_OIDC_SP_HOST
      - GRIST_OIDC_IDP_ISSUER
      - GRIST_OIDC_IDP_CLIENT_ID
      - GRIST_OIDC_IDP_CLIENT_SECRET
      - GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT
      - GRIST_ANON_PLAYGROUND

      - GRIST_DEFAULT_EMAIL
      - GRIST_PAGE_TITLE_SUFFIX
      - GRIST_SESSION_SECRET
      - GRIST_HOME_INCLUDE_STATIC
      - GRIST_ORG_IN_PATH

      - GRIST_SUPPORT_EMAIL
      - GRIST_HIDE_UI_ELEMENTS
      - GRIST_SANDBOX_FLAVOR
      - GRIST_EXPERIMENTAL_PLUGINS
      - GRIST_BOOT_KEY
      - GRIST_WIDGET_LIST_URL

      - ALLOWED_WEBHOOK_DOMAINS
      - COMMENTS
    volumes:
      - "./.state/grist:/persist"
    networks:
      - internal
      - web
    healthcheck:
      test: ["CMD-SHELL", "python -c \"import urllib.request; req=urllib.request.Request('http://127.0.0.1:8484/api/orgs'); req.add_header('Host', '${GRIST_DOMAIN}'); urllib.request.urlopen(req).getcode()\""]
      start_period: 10s
      interval: 10s
      retries: 5
      timeout: 3s
    labels:
      traefik.enable: true
      traefik.http.middlewares.permanent-redirect.redirectscheme.scheme:    https
      traefik.http.middlewares.permanent-redirect.redirectscheme.permanent: true
      traefik.http.routers.org-example-grist-redirect.entrypoints:           web
      traefik.http.routers.org-example-grist-redirect.middlewares:           permanent-redirect
      traefik.http.routers.org-example-grist-redirect.rule:                  Host(`${FQDN}`)

      traefik.http.services.org-example-grist.loadbalancer.server.port: 8484

      traefik.http.routers.org-example-grist-general.rule:             Host(`${FQDN}`)
      traefik.http.routers.org-example-grist-general.entrypoints:      web-secure
      traefik.http.routers.org-example-grist-general.service:          org-example-grist
      traefik.http.routers.org-example-grist-general.tls:              true
      traefik.http.routers.org-example-grist-general.tls.certresolver: letsencrypt

  postgres:
    image: postgres:15-alpine
    command: -c jit=off
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./.state/postgres:/var/lib/postgresql/data
    networks:
      - internal
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}"]
      start_period: 10s
      interval: 10s
      retries: 5
      timeout: 3s

  redis:
    image: redis:6-alpine
    command: ["redis-server","/usr/local/etc/redis/redis.conf"]
    environment:
      - REDIS_URL
    volumes:
      - "./.state/redis:/data"
      - "./redis.conf:/usr/local/etc/redis/redis.conf"
    networks:
      - internal
    healthcheck:
      test: ["CMD-SHELL", "redis-cli -u ${REDIS_URL} ping | grep 'PONG' || exit 1"]
      start_period: 10s
      interval: 10s
      retries: 5
      timeout: 3s

We’re not (yet) using Minio with a snapshotting backend, why it is commented out.

  • redis.conf
cat > redis.conf <<< "requirepass $(openssl rand -base64 48)"
  • .env
FQDN=grist.example.org
EMAIL=anonymous@example.org

GRIST_DOMAIN=${FQDN}
APP_HOME_URL=https://${FQDN}

COOKIE_SECRET=<openssl rand -base64 48>
GRIST_SESSION_SECRET=<openssl rand -base64 48>

GRIST_BOOT_KEY=<openssl rand -base64 48>

POSTGRES_DB=org-example-grist
POSTGRES_USER=org-example-grist
POSTGRES_PASS=<openssl rand -base64 48>

TYPEORM_TYPE=postgres
TYPEORM_HOST=postgres
TYPEORM_DATABASE=${POSTGRES_DB}
TYPEORM_USERNAME=${POSTGRES_USER}
TYPEORM_PASSWORD=${POSTGRES_PASS}

REDIS_SECRET=<secret from redis.conf>
REDIS_URL=redis://default:${REDIS_SECRET}@redis:6379/1

# Requires MinIO w/ EC, at least 4 XFS drives
# GRIST_DOCS_MINIO_ENDPOINT=minio.example.org
# GRIST_DOCS_MINIO_USE_SSL=true
# GRIST_DOCS_MINIO_BUCKET_REGION=eu-central-1
# GRIST_DOCS_MINIO_BUCKET=org-example-grist
# GRIST_DOCS_MINIO_ACCESS_KEY=org.example.grist
# GRIST_DOCS_MINIO_SECRET_KEY=<openssl rand -base64 48>

GRIST_OIDC_SP_HOST=https://${FQDN}
GRIST_OIDC_IDP_ISSUER=https://gitlab.example.org/.well-known/openid-configuration
GRIST_OIDC_IDP_CLIENT_ID=
GRIST_OIDC_IDP_CLIENT_SECRET=
GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true
GRIST_ANON_PLAYGROUND=false

GRIST_DEFAULT_EMAIL=${EMAIL}
GRIST_PAGE_TITLE_SUFFIX=_blank
GRIST_HOME_INCLUDE_STATIC=true
GRIST_ORG_IN_PATH=true

GRIST_SUPPORT_EMAIL=support@example.org
GRIST_HIDE_UI_ELEMENTS=helpCenter,billing
#GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts
GRIST_SANDBOX_FLAVOR=unsandboxed
#GRIST_SANDBOX_FLAVOR=gvisor
GRIST_EXPERIMENTAL_PLUGINS=true
GRIST_WIDGET_LIST_URL=https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json

ALLOWED_WEBHOOK_DOMAINS=n8n.example.org,mattermost.example.org,gitlab.example.org
COMMENTS=true

This should get everyone started with a Grist instance behind Traefik. Ping me, if you have any questions or comments.

I’m posting this now, because I have just come up with this healthcheck: for the Grist container. This or something similar could also be added to the Dockerfile as a HEALTHCHECK line, in so all container schedulers can benefit from it’s presence. The easiest would probably to also add a minor curl dependency on the final container image and check the status code of the /api/orgs route (the only one without parameters) with it instead of this Python one-liner construct.

1 Like