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.