# Pneumatic Workflow Configuration # This is a configuration file listing all parameters and their default values. # After changing the parameters, you need to restart the Docker containers. # ============================================================================= # Server address # ============================================================================= # SERVER_ADDRESS=localhost # BACKEND_URL=http://localhost:8001 # FRONTEND_URL=http://localhost # FORMS_URL=http://localhost/forms # WSS_URL=ws://localhost:8001 # Custom form domain works only with SSL=yes # If FORM_DOMAIN is specified, use NGINX_CONF_TEMPLATE=./nginx/ssl_forms_templates/ # FORM_DOMAIN= # ============================================================================= # Nginx / SSL # ============================================================================= # SSL=no # NGINX_CONF_TEMPLATE=./nginx/ssl_templates/ # --- Certbot --- # CERTBOT_ENABLE=no # CERTBOT_EMAIL= # ============================================================================= # Database # ============================================================================= # --- PostgreSQL --- # POSTGRES_PASSWORD=postgres_password # POSTGRES_REPLICA_PASSWORD=postgres_password # POSTGRES_HOST=postgres # POSTGRES_USER=postgres_user # POSTGRES_DB=postgres_db # POSTGRES_REPLICA_HOST=postgres # POSTGRES_REPLICA_USER=postgres_user # POSTGRES_REPLICA_DB=postgres_db # --- Redis --- # Cache (db0), auth tokens (db1), websocket channels (db2), sessions (db3) and # the event buffer (db4, see Logging / Events below). # REDIS_PASSWORD=redis_password # --- RabbitMQ --- # RABBITMQ_USER=rabbitmq_user # RABBITMQ_PASSWORD=rabbitmq_password # ============================================================================= # Application # ============================================================================= # ENVIRONMENT=Production # LANGUAGE_CODE=en # --- Backend --- # WORKERS_COUNT=2 # DJANGO_DEBUG=no # ADMIN_PATH=admin # DJANGO_SECRET_KEY=django_secret_django_secret_django_secret # ENABLE_LOGGING=no # SENTRY_DSN= # --- Frontend --- # NODE_OPTIONS=--max-old-space-size=3072 # SENTRY_FRONTEND_DSN= # MCS_RUN_ENV=prod # --- File service --- # SENTRY_FILE_DSN= # ============================================================================= # SIGNUP # ============================================================================= # SIGNUP=yes # ============================================================================= # Authorization # ============================================================================= # --- Microsoft Entra ID (AD) --- # MS_AUTH=no # MS_CLIENT_ID= # MS_CLIENT_SECRET= # MS_AUTHORITY= # --- Google --- # GOOGLE_AUTH=no # GOOGLE_OAUTH2_CLIENT_ID= # GOOGLE_OAUTH2_CLIENT_SECRET= # GOOGLE_OAUTH2_REDIRECT_URI= # --- SSO --- # SSO_AUTH=no # Okta # SSO_PROVIDER=okta # OKTA_DOMAIN= # OKTA_CLIENT_ID= # OKTA_CLIENT_SECRET= # OKTA_REDIRECT_URI= # Auth0 # SSO_PROVIDER=auth0 # AUTH0_CLIENT_ID= # AUTH0_CLIENT_SECRET= # AUTH0_DOMAIN= # AUTH0_REDIRECT_URI= # ============================================================================= # Email # ============================================================================= # EMAIL=no # --- Server --- # EMAIL_PROVIDER=smtp # EMAIL_HOST= # EMAIL_PORT= # EMAIL_HOST_USER= # EMAIL_HOST_PASSWORD= # EMAIL_USE_TLS= # EMAIL_USE_SSL= # EMAIL_TIMEOUT= # --- Customer.io --- # EMAIL_PROVIDER=customerio # CIO_WEBHOOK_API_VERSION= # CIO_WEBHOOK_API_KEY= # CIO_TRANSACTIONAL_API_KEY= # CIO_TEMPLATE__RESET_PASSWORD= # CIO_TEMPLATE__INVITE= # CIO_TEMPLATE__USER_DEACTIVATED= # CIO_TEMPLATE__NEW_TASK= # CIO_TEMPLATE__TASK_RETURNED= # CIO_TEMPLATE__ACCOUNT_VERIFICATION= # CIO_TEMPLATE__WORKFLOWS_DIGEST= # CIO_TEMPLATE__TASKS_DIGEST= # CIO_TEMPLATE__USER_TRANSFER= # CIO_TEMPLATE__UNREAD_NOTIFICATIONS= # CIO_TEMPLATE__GUEST_NEW_TASK= # CIO_TEMPLATE__OVERDUE_TASK= # CIO_TEMPLATE__MENTION= # CIO_TEMPLATE__TASK_REMINDER= # CIO_TEMPLATE__COMPLETE_WORKFLOW= # CIO_TEMPLATE__VACATION_DELEGATION= # ============================================================================= # Captcha # ============================================================================= # CAPTCHA=no # RECAPTCHA_SITE_KEY= # RECAPTCHA_SECRET_KEY= # RECAPTCHA_TESTING=no # ============================================================================= # AI # ============================================================================= # AI=no # --- OpenAI --- # AI_PROVIDER=openai # OPENAI_API_KEY= # OPENAI_API_ORG= # AI_HTTP_PROXY=http://user:pass@proxy.example:8080 # AI_HTTPS_PROXY=http://user:pass@proxy.example:8080 # ============================================================================= # Push notifications # ============================================================================= # PUSH=no # --- Firebase --- # PUSH_PROVIDER=firebase # FIREBASE_VAPID_KEY= # FIREBASE_API_KEY= # FIREBASE_AUTH_DOMAIN= # FIREBASE_PROJECT_ID= # FIREBASE_STORAGE_BUCKET= # FIREBASE_SENDER_ID= # FIREBASE_APP_ID= # FIREBASE_MEASUREMENT_ID= # FIREBASE_PUSH_APPLICATION_CREDENTIALS=/pneumatic_backend/firebase-push.json # ============================================================================= # Analytics # ============================================================================= # ANALYTICS=no # --- Segment --- # ANALYTICS_WRITE_KEY= # ANALYTICS_DEBUG=no # ============================================================================= # Logging / Events # ============================================================================= # User action events are buffered in Redis and forwarded by the OpenTelemetry # collector to the store LOGS_BACKEND names. The journal is off by default: # none of its containers start and nothing is written to Redis. # otlp / elasticsearch = a store outside this machine; unset or empty = # pipeline off (the default). Nothing that stores or shows the journal runs # here: the collector forwards the events, the store lives on a machine of its # own with a compose file of its own - logging/grafana for Loki + Grafana, # logging/elasticsearch for Elasticsearch + Kibana. start.sh reads this value # and starts the collector on its own; running compose by hand needs the # COMPOSE_PROFILES line at the bottom of this section as well. The defaults # of the other LOGS_* values below are in x-logs-environment of the compose # files. # LOGS_BACKEND=otlp # LOGS_OTLP_ENDPOINT=http://otel-collector:4318 # The file service writes into the same journal (uploads, downloads and # refused downloads) and reads LOGS_BACKEND, REDIS_PASSWORD and # LOGS_STREAM_MAXLEN from this file too; there is nothing to configure for it # separately. Deploy order on an upgrade: backend first, then the file service. # The backend declares the file event types; a file service ahead of it would # have its records filed under the other category with the file name exposed. # --- Event buffer: db4 of the Redis above --- # Events wait in a Redis stream between emit() and the collector, in db4 of the # shared Redis; REDIS_PASSWORD opens it. The stream is the only copy of an # event until the collector has it, and that Redis keeps RDB snapshots only, # so a crash costs at most ~20 seconds of events. # How many events the buffer keeps before the oldest are dropped. It is also # the memory budget: an event costs ~700 B in the stream (plan with 1 KB), so # the default 250 000 is ~250 MB, plus 10 MB of dead letter. # What it buys is how long the collector may be down without losing events: # ~69 hours at 1 event/s, ~7 hours at 10/s, ~42 minutes at 100/s. The rate of # workflow events, the bulk of the journal, over the last day: # select count(*) / 86400.0 from processes_workflowevent # where created > now() - interval '1 day'; # LOGS_STREAM_MAXLEN=250000 # Events in one POST to the collector. A batch refused with 413 (body too # large for the collector or a proxy in front of it) goes to the dead # letter: lower this value. # LOGS_CONSUMER_BATCH_SIZE=1000 # --- OTLP receiver outside this machine (LOGS_BACKEND=otlp) --- # The Loki of logging/grafana/docker-compose.yml takes the records at # http://:3100/otlp; a stack of your own takes them wherever # its OTLP/HTTP receiver listens. # LOGS_OTLP_EXPORT_ENDPOINT= # The whole Authorization header value ("Bearer ...", "Basic ..."); it is # sent even when empty, see otel-collector/backend-otlp.yaml. # LOGS_OTLP_EXPORT_AUTH= # --- Elasticsearch (LOGS_BACKEND=elasticsearch) --- # The store runs on a machine of its own, see logging/elasticsearch. This # machine needs the variables below, the CA file of the store and, once, a # queue volume the collector can write into. The collector image runs as # uid 10001 and a fresh named volume belongs to root, so before the first # start: # docker volume create pneumatic_otel-queue # docker run --rm -v pneumatic_otel-queue:/q alpine chown 10001:10001 /q # Afterwards the collector log has no "permission denied", and # docker run --rm -v pneumatic_otel-queue:/q alpine ls -l /q/queue # shows the queue database. # Address of the store. https, always: this is the audit journal of every # account in the installation, and the host name has to be one of the names # the certificate was issued for (ES_CERT_DNS on the storage machine). # LOGS_ELASTICSEARCH_URL=https://es.pneumatic.internal:9200 # How the collector authenticates. An API key is the way: it carries a role # that can append to the journal and nothing else, it expires on a date of its # own, and rotating it is this one line plus a restart - see # logging/elasticsearch/scripts/rotate-api-key.sh, which prints the value. # LOGS_ELASTICSEARCH_API_KEY= # The login and password of an Elasticsearch account, as an alternative to the # key above. Set one or the other, never both. A password is worse: it belongs # to a person, it has no expiry date, and changing it takes the account away # from everything else that used it. # LOGS_ELASTICSEARCH_USER= # LOGS_ELASTICSEARCH_PASSWORD= # The certificate authority of the store, inside the collector container. The # file is logging/elasticsearch/certs/ca/ca.crt on the storage machine; copy # it to otel-collector/es-ca.crt here - that directory is already mounted # read only, so no compose change is needed. An empty value means the system # certificate store, for a store with a publicly trusted certificate. # LOGS_ELASTICSEARCH_CA_FILE=/etc/otel/es-ca.crt # The data stream the events are written to. The name is not free-form: # `logs-.otel-` is what Elasticsearch recognises as an # OTel log stream, and it is what the index template, the retention policy and # the writer role are bound to. Change it only together with ES_DATA_STREAM # and ES_INDEX_PATTERN on the storage machine. # LOGS_ELASTICSEARCH_INDEX=logs-pneumatic.events.otel-default # Where the collector keeps the queue of batches it has not delivered yet. # The default is the mount point of the otel-queue volume of the collector; # there is no reason to change it. # LOGS_ELASTICSEARCH_QUEUE_DIR=/var/lib/otelcol/queue # One thing to watch in this mode. A store that cannot be reached at all # (connection refused, name does not resolve) makes the exporter drop the # batch instead of retrying it, and by then the consumer has been answered # and the records are out of the buffer. So: stop the collector before # restarting the store, and watch the counters it publishes on :8888 - # docker exec pneumatic-otel-collector wget -qO- localhost:8888/metrics \ # | grep -E 'send_failed_log_records|queue_size' # The first counter is events lost for good, the second is how full the # disk queue is. otel-collector/backend-elasticsearch.yaml has the details. # --- Start the services of the mode --- # Only for running `docker compose` by hand: start.sh derives the same from # LOGS_BACKEND above and passes it itself. Uncomment the lines matching # LOGS_BACKEND. Without them the collector does not start, and a pipeline that # is on piles events up in the buffer until LOGS_STREAM_MAXLEN drops the oldest. # otlp: # COMPOSE_PROFILES=logs-otlp # elasticsearch: # COMPOSE_PROFILES=logs-elasticsearch # ============================================================================= # File Service # ============================================================================= # FILE_SERVICE_URL=http://localhost/files # FILE_DEBUG=false # FILE_CONFIG=Production # FILE_WORKERS=1 # FILE_PORT=8000 # FILE_FASTAPI_BASE_URL=http://localhost/files # FILE_MAX_FILE_SIZE=104857600 # FILE_BUCKET_PREFIX=pneumatic-dev-test # FILE_CHUNK_SIZE=1048576 # --- File Service Auth (internal, must match Django backend) --- # FILE_KEY_PREFIX_REDIS=:1: # FILE_AUTH_TOKEN_ITERATIONS=1 # --- File Service Database --- # FILE_POSTGRES_USER=pneumatic # FILE_POSTGRES_PASSWORD=pneumatic # FILE_POSTGRES_DB=pneumatic # FILE_POSTGRES_HOST=postgres # FILE_POSTGRES_PORT=5432 # --- File Service Storage: Local (SeaweedFS) --- # FILE_STORAGE_TYPE=local # FILE_SEAWEEDFS_S3_ENDPOINT=http://seaweedfs-filer:8333 # FILE_SEAWEEDFS_S3_ACCESS_KEY=any-secret-key-will-work # FILE_SEAWEEDFS_S3_SECRET_KEY=any-secret-key-will-work # FILE_SEAWEEDFS_S3_REGION=us-central1 # FILE_SEAWEEDFS_S3_USE_SSL=false # --- File Service Storage: Google Cloud --- # To use GCS, set FILE_STORAGE_TYPE=google and fill in the values below. # FILE_GCS_S3_ENDPOINT= # FILE_GCS_S3_ACCESS_KEY= # FILE_GCS_S3_SECRET_KEY= # FILE_GCS_S3_REGION= # ============================================================================= # Billing # ============================================================================= # BILLING=no # --- Stripe --- # STRIPE_SECRET_KEY= # STRIPE_WEBHOOK_SECRET= # STRIPE_WEBHOOK_IP_WHITELIST=