Skip to content

Discourse: Open-Source Community Forum

Deploy Discourse with Pigsty-managed PostgreSQL and a single Redis instance embedded in the application container.

Discourse is open-source community forum software. Pigsty’s app/discourse template builds the application image locally with the official classic discourse_docker Launcher and runs it with Docker Compose. PostgreSQL is managed externally by Pigsty; a single Redis instance runs inside the application container.

The template follows app.yml → application directory → .env → make. apps.discourse.dest is /data/app/discourse, keeping managed and persistent files together for migration. Use app/discourse/, conf/app/discourse.yml, and files/discourse.sql from the same Pigsty version.

ItemDefault
Forum domainask.pigsty
PostgreSQL10.10.10.10:5432; database and user both discourse
RedisSingle instance inside the application container, 127.0.0.1:6379
Public HTTP/HTTPSPigsty Nginx, 80 / 443
Nginx upstreamunix:/data/app/discourse/shared/nginx.http.sock
Application and management files/data/app/discourse

The template creates no PostgreSQL container and publishes neither Redis nor HTTP ports. Pigsty Nginx proxies through a Unix socket, avoiding a conflict with its own port 80. Redis binds only to container loopback and is not shared with other applications or native Pigsty Redis. Build and runtime use the same default Docker bridge, keeping database reachability and source addresses consistent.


Quick Start

On a fresh host running a compatible OS:

curl -fsSL https://repo.pigsty.io/get | bash; cd ~/pigsty
./bootstrap                    # Install Ansible
./configure -c app/discourse   # Select the Discourse template
vi pigsty.yml                  # Change passwords, domain, and SMTP
./deploy.yml                   # Install Pigsty and PostgreSQL
./docker.yml                   # Install Docker and Compose
./app.yml -l discourse         # Build and start Discourse

Resolve ask.pigsty to the Nginx host, then visit http://ask.pigsty. The template writes the local mapping through node_etc_hosts. This name is for local tests, not public certificates.

For an existing Pigsty deployment, merge the application definition, discourse user/database, and portal into the inventory, preserving existing clusters, hosts, users, databases, and portals. Do not rerun configure over a production inventory or reinitialize running clusters. Review the merged configuration, create only the new user and database, install pgvector as needed, apply HBA and portal changes, then start the application.


Configuration Template

The source is conf/app/discourse.yml; see the full app/discourse template. app.yml copies app/discourse to /data/app/discourse and writes .env from apps.discourse.conf. Common settings:

ParameterDefaultDescription
DISCOURSE_DATA/data/app/discourseApplication directory; keep consistent with dest
DISCOURSE_HOSTNAMEask.pigstyDNS name only, without URL scheme or port
DISCOURSE_VERSIONf00f07d62728d646642cd2185cc077ad84a60fa8Pinned Discourse application commit
DISCOURSE_DOCKER_REF0ba1841d589e77df3bc923774abca2720a320ec2Pinned classic Launcher and template commit
PG_HOST / PG_PORT10.10.10.10 / 5432Direct PostgreSQL endpoint; cannot be localhost
PG_DBNAME / PG_USERNAMEdiscourse / discourseDatabase and owner
PG_PASSWORDDBUser.DiscourseMust match pg_users; replace in production
UNICORN_WORKERS2Web workers
UNICORN_SIDEKIQS1Sidekiq processes
DISCOURSE_SIDEKIQ_WORKERS5Background job threads
DISCOURSE_DB_POOL8Connections per process, not the application total
REDIS_MAX_MEMORY512mbEmbedded Redis maxmemory
DISCOURSE_FORCE_HTTPSfalseSet true after configuring a trusted HTTPS portal
DISCOURSE_DEVELOPER_EMAILS[email protected]Administrator email; change before deployment
DISCOURSE_SMTP_ADDRESS / DISCOURSE_SMTP_PORTsmtp.example.com / 587SMTP server; change before deployment
DISCOURSE_NOTIFICATION_EMAIL[email protected]Notification sender; change before deployment
DISCOURSE_SKIP_EMAIL_SETUP0Set to 1 only for isolated tests
DISCOURSE_IMAGElocal_discourse/discourse:latestImage built locally by Launcher

Configure SMTP and the administrator email before startup. The sample SMTP host intentionally causes the build to fail until replaced. DISCOURSE_SKIP_EMAIL_SETUP=1 is for isolated tests and does not provide working email. Public deployments need a real domain, valid TLS certificate, and DISCOURSE_FORCE_HTTPS=true.


Database Requirements

Use PostgreSQL 18, UTF8 encoding, and hstore, pg_trgm, unaccent, and vector. Pigsty creates these extensions as DBA. The discourse owner performs migrations without SUPERUSER, CREATEDB, or a shared administrator role; the template revokes dbrole_admin membership. files/discourse.sql overrides shared default ACLs for this owner.

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' }

Connect directly to PostgreSQL, not PgBouncer transaction pooling: Discourse requires advisory locks. For HA, use a direct PostgreSQL endpoint that routes only to the primary, and point the backup endpoint to the same writer. A fixed node’s 5432 does not follow failover.

The default Docker HBA rule permits only this user/database from the Docker bridge. Existing firewalls must allow bridge-to-database traffic. The template does not change firewall rules or unrelated demo routes.


Image Builds and Operations

cd /data/app/discourse
make config      # View the Launcher definition with secrets redacted
make up          # Build initially, start, and wait for HTTP health
make view        # Print the URL and Nginx upstream
make health      # Check /srv/status through the Unix socket
make log         # Follow container logs
make stop        # Stop the container
make start       # Start an existing image whose configuration is unchanged
make rebuild     # Stop, rebuild/migrate, then start
make clean       # Remove the container; retain files and PostgreSQL
make save        # Save the image to /tmp/docker/discourse.tgz
make load        # Load the image from /tmp/docker/discourse.tgz

DISCOURSE_DOCKER_REF pins both the classic Launcher and templates. DISCOURSE_VERSION also defaults to an exact validated application commit for consistent builds across hosts and time. make up reuses the image when its build record matches the resolved configuration. Use make rebuild after changing pinned commits, or refreshing an explicitly selected floating branch. Check both references and the required PostgreSQL major version when upgrading.

The generated launcher/containers/discourse.yml uses JSON syntax, which is also valid YAML, with mode 0600. It uses Redis, Web, rate-limit, and socket templates, excluding PostgreSQL, SSL, and Let’s Encrypt templates. The image is built locally; it does not use the experimental prebuilt discourse/discourse image.

Builds stop the old application before bootstrap, because bootstrap also starts Redis against shared data. Do not bootstrap another instance against an active shared/redis_data, run two copies of this application, or use Launcher V2’s external-database auto-rebuild with this embedded Redis layout. After a failed build, the old application stays stopped. Diagnose and fix the error before starting an image against a database that may already have been migrated.

Builds require Linux, Docker, Compose v2.20+, Git, and Python 3. Launcher supports x86-64; upstream describes ARM64 support as experimental. Initial builds need access to Docker Hub, GitHub, gems, and frontend dependencies, plus spare CPU, memory, and disk. Defaults are two Web workers, one Sidekiq process, five job threads, and eight database connections per process. An 8 GiB host can run a small forum with Pigsty if capacity is reserved for monitoring and builds; 16 GiB provides more maintenance headroom.


Persistent Data and Backups

/data/app/discourse/
├── launcher/                      # Pinned upstream source and private application definition
├── .build.json                    # Configuration fingerprint, image ID, and exact commits
└── shared/
    ├── uploads/                   # Originals, attachments, and derived images
    ├── redis_data/                # RDB / complete AOF file set
    ├── backups/default/           # Discourse application archives
    ├── log/                       # Rails, Sidekiq, Redis, and system logs
    ├── state/                     # logrotate / anacron state
    └── tmp/                       # Backup and restore workspace

Redis stores background queues as well as cache. It uses maxmemory 512mb, noeviction, AOF everysec, and upstream RDB snapshots; it is not disposable cache. The memory limit does not cap total RSS or rewrite peaks. Monitor memory, job latency, persistence failures, and disk use.

Back up PostgreSQL with Pigsty/pgBackRest. Also create Discourse archives including uploads and keep an independent copy on another host. Copying one live .aof file is not a valid multipart AOF backup; use a stable RDB or consistent complete file set. Isolated restore tests need a separate database, Redis, and shared directory with outbound email disabled: Discourse restoration clears Redis and queues.

Uploads are local by default. Configure MinIO/Silo/S3 only after checking endpoint/bucket DNS, TLS, CORS, and private-upload permissions. S3 uploads are excluded from archives unless explicitly configured, so back up those objects separately.


Migrate the Application

  1. Record the running image ID and source versions, and back up the database.
  2. Stop the old application and confirm all Web, Sidekiq, and Redis processes have exited.
  3. Copy /data/app/discourse through a private channel, preserving ownership and permissions and excluding the temporary Nginx socket.
  4. Use make save / make load to transfer the same image. /tmp/docker/discourse.tgz is private: Launcher-built images contain application configuration and must not enter Git or public registries.
  5. Configure database connectivity and the Nginx socket upstream on the new host. With unchanged configuration and image, use make start. Verify login, posting, uploads, email, background jobs, and backups before switching the public proxy. Keep the old application stopped.

If the database endpoint or other configuration changes, retain the exact DISCOURSE_VERSION and DISCOURSE_DOCKER_REF from the old build record, then use make up to rebuild the same version. Avoid combining migration with an application upgrade.

If PostgreSQL stays in place, no Discourse restore is needed. Moving or retiring the database host is a separate Pigsty switchover or recovery operation. Never copy live PGDATA as part of the application directory.


Known Limitations

  • After a failed rebuild, the old .build.json may still let make up or make start connect the old image to a possibly migrated database. Do not use these commands to recover from failed bootstrap before checking schema and image state. Fix build-record invalidation before relying on this workflow for production upgrades.
  • files/discourse.sql assumes both database and owner are named discourse. Retain these names or update SQL, inventory, and .env together. Verify baseline success after deployment: the database playbook currently ignores baseline errors and could leave shared-role grants behind.
  • The default oltp Tuned profile sets vm.overcommit_memory=0, while Redis persistence requires 1. Configure a persistent override and recheck after restarting Tuned or the host; the application template does not handle this yet.

Validation Record

On 2026-10-14, an isolated Linux ARM64 Docker environment built application commit 3279b67fba94db51b9a545b90f8eaf4352cae0bb with the pinned Launcher and external PostgreSQL 18.6. Schema migrations, the initial setup page, and health endpoint passed through the rendered Pigsty Nginx socket portal. Redis ran as one process with loopback binding, AOF, noeviction, and a 512 MB limit; test keys survived container removal and recreation. The database baseline passed repeated execution and denied shared roles access to newly created owner objects.

A native x86-64 deployment the same day used the same application and Launcher commits with Pigsty PostgreSQL 18.6. It validated public HTTPS, Chinese assets, administrator login/API, Redis persistence settings, and Ruby SMTP TLS/authentication checks. Application archives contained SQL and local uploads; the off-host copy had the same SHA-256. Repeated make up reused the existing image.

Actual email delivery, complete regular-user posting/upload flows, load capacity, backup recovery, and failover remain unverified.


References