# app/discourse

> Deploy Discourse with Pigsty-managed PostgreSQL and Redis embedded in the application container

---

LLMS index: [llms.txt](/llms.txt)

---

The `app/discourse` template provides a single-node Discourse forum. Pigsty manages PostgreSQL, while Redis runs inside the application container. Pigsty Nginx serves Discourse through a Unix socket.

This template was added after v4.5.0. Use the matching `app/discourse/` directory and `files/discourse.sql` from the same checkout; the YAML alone is not sufficient.

See the **[Discourse application README](https://github.com/pgsty/pigsty/tree/main/app/discourse#readme)** for more details.

--------

## Overview

- Config Name: `app/discourse`
- Node Count: Single node
- OS Distro: `el8`, `el9`, `el10`, `d12`, `d13`, `u22`, `u24`, `u26`
- OS Arch: `x86_64` (upstream Launcher describes ARM64 support as experimental)
- Related: [`app/odoo`](/docs/conf/odoo/), [`app/mattermost`](/docs/conf/mattermost/)

For a fresh host:

```bash
./configure -c app/discourse [-i <primary_ip>]
vi pigsty.yml              # change passwords, domain, and SMTP settings
./deploy.yml
./docker.yml
./app.yml -l discourse
```

--------

## Content

Source: [`pigsty/conf/app/discourse.yml`](https://github.com/pgsty/pigsty/blob/main/conf/app/discourse.yml)

```yaml
---
#==============================================================#
# File      :   discourse.yml
# Desc      :   Pigsty Discourse with external PostgreSQL / embedded Redis
# Ctime     :   2026-10-14
# Mtime     :   2026-10-14
# License   :   Apache-2.0 @ https://pigsty.io/docs/about/license/
# Copyright :   2018-2026  Ruohang Feng / Vonng (rh@vonng.com)
#==============================================================#
# Fresh host: ./configure -c app/discourse; edit pigsty.yml (passwords + SMTP)
# ./deploy.yml; ./docker.yml; ./app.yml -l discourse
# Existing Pigsty: merge the app/user/database/portal sections; do NOT replace
# the running inventory or initialize an existing PostgreSQL cluster again.
# ask.pigsty -> Pigsty Nginx :80/:443 -> Unix socket -> Discourse + Redis.

all:
  children:
    discourse:
      hosts: { 10.10.10.10: {} }
      vars:
        app: discourse
        apps:
          discourse:
            dest: /data/app/discourse
            file:
              - { path: /data/app/discourse                    ,state: directory ,mode: '0755' }
              - { path: /data/app/discourse/shared             ,state: directory ,mode: '0755' }
              - { path: /data/app/discourse/shared/log/var-log ,state: directory ,mode: '0755' }
            conf:
              DISCOURSE_DATA: /data/app/discourse
              DISCOURSE_HOSTNAME: ask.pigsty
              DISCOURSE_VERSION: tests-passed
              DISCOURSE_DOCKER_REF: 945d491ee62ac0d6e5d9076c173073c3f795d978
              PG_HOST: 10.10.10.10
              PG_PORT: 5432
              PG_DBNAME: discourse
              PG_USERNAME: discourse
              PG_PASSWORD: DBUser.Discourse
              UNICORN_WORKERS: 2
              UNICORN_SIDEKIQS: 1
              DISCOURSE_SIDEKIQ_WORKERS: 5
              DISCOURSE_DB_POOL: 8
              REDIS_MAX_MEMORY: 512mb
              DISCOURSE_FORCE_HTTPS: false  # set true with a trusted HTTPS portal
              DISCOURSE_DEVELOPER_EMAILS: admin@example.com  # CHANGE ME
              DISCOURSE_SMTP_ADDRESS: smtp.example.com       # CHANGE ME
              DISCOURSE_SMTP_PORT: 587
              DISCOURSE_SMTP_USER_NAME: ''
              DISCOURSE_SMTP_PASSWORD: ''
              DISCOURSE_SMTP_ENABLE_START_TLS: true
              DISCOURSE_SMTP_FORCE_TLS: false
              DISCOURSE_NOTIFICATION_EMAIL: noreply@example.com  # CHANGE ME

    pg-discourse:
      hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
      vars:
        pg_cluster: pg-discourse
        pg_extensions: [ pgvector ]
        pg_users:
          - name: discourse
            password: DBUser.Discourse  # CHANGE ME, same as PG_PASSWORD above
            pgbouncer: false
            roles: [{ name: dbrole_admin, state: revoke }]
            comment: Discourse database owner
        pg_databases:
          - name: discourse
            owner: discourse
            encoding: UTF8
            revokeconn: true
            pgbouncer: false
            baseline: discourse.sql
            extensions: [ hstore, pg_trgm, unaccent, vector ]
            comment: Discourse forum
        pg_hba_rules:
          - { user: discourse ,db: discourse ,addr: 172.16.0.0/12 ,auth: pwd ,title: 'Discourse from local Docker bridges' }
        pg_crontab: [ '00 01 * * * /pg/bin/pg-backup full' ]

    infra: { hosts: { 10.10.10.10: { infra_seq: 1 } } }
    etcd:  { hosts: { 10.10.10.10: { etcd_seq: 1 } }, vars: { etcd_cluster: etcd } }

  vars:
    version: v4.5.0
    admin_ip: 10.10.10.10
    region: default
    node_tune: oltp
    pg_conf: oltp.yml
    pg_version: 18
    docker_enabled: true
    repo_enabled: false
    node_repo_modules: node,infra,pgsql
    node_etc_hosts: ['${admin_ip} i.pigsty ask.pigsty']
    infra_portal:
      home: { domain: i.pigsty }
      discourse:
        domain: ask.pigsty
        endpoint: unix:/data/app/discourse/shared/nginx.http.sock
        websocket: true
        config: |
          client_max_body_size 10m;

    # CHANGE these demo credentials before any public deployment.
    grafana_admin_password: pigsty
    grafana_view_password: DBUser.Viewer
    pg_admin_password: DBUser.DBA
    pg_monitor_password: DBUser.Monitor
    pg_replication_password: DBUser.Replicator
    patroni_password: Patroni.API
    haproxy_admin_password: pigsty
    etcd_root_password: Etcd.Root
...
```

--------

## Explanation

The template builds the Discourse image locally with the official classic Launcher and runs it with Docker Compose, using PostgreSQL managed by Pigsty.

- `apps.discourse.dest` is `/data/app/discourse`; managed files and persistent data under `shared/` live in this directory.
- `DISCOURSE_DOCKER_REF` pins the Launcher commit, while `DISCOURSE_VERSION` follows the upstream `tests-passed` channel by default.
- The `pg-discourse` cluster uses PostgreSQL 18 and installs `pgvector`. The `discourse` database enables `hstore`, `pg_trgm`, `unaccent`, and `vector`, and applies the `discourse.sql` baseline.
- The `discourse` user has `dbrole_admin` revoked. Neither the user nor the database uses PgBouncer.
- The application HBA rule allows the `discourse` user to reach the `discourse` database from Docker networks in `172.16.0.0/12`.
- The `ask.pigsty` Nginx portal forwards to `unix:/data/app/discourse/shared/nginx.http.sock`, enables WebSocket, and sets `client_max_body_size 10m`.
- Embedded Redis has a `REDIS_MAX_MEMORY: 512mb` limit.
- A full PostgreSQL backup runs daily at 01:00.

Access the forum at `http://ask.pigsty`. The template adds this name to the local hosts file through `node_etc_hosts`; other clients need DNS or hosts entries pointing to the Nginx host.

## Notes

- Change `PG_PASSWORD` and the password in `pg_users` together. Configure the administrator email and SMTP settings; the sample SMTP hostname will cause the image build to fail.
- For public access, use a real domain and trusted certificate, and set `DISCOURSE_FORCE_HTTPS: true`.
- Discourse uses advisory locks and requires a direct PostgreSQL connection. Do not use PgBouncer transaction pooling. For HA, choose a direct service that routes only to the primary.
- In an existing Pigsty environment, merge the application, user, database, and portal definitions. Do not rerun `configure` or reinitialize an existing cluster.
- Redis persistence needs `vm.overcommit_memory=1`. The Tuned profile selected by `node_tune: oltp` uses `0`; configure a persistent override before enabling Redis persistence.
