Discourse: move from Bitnami chart to launcher-built image
The Bitnami chart and images stop at 3.5.0. Run Discourse 2026.10.0 from an image built with the official launcher, on PostgreSQL 18, reusing the existing uploads volume, Redis and secrets. Update the Dex sign-in step for the new image paths, drop the removed LDAP setting, and keep chat off now that it is bundled in core. The image adds a real-IP rule so visitors are not all rate limited as the ingress. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,12 +6,13 @@ SIGNUP_URL = "https://www.eom.dev/members"
|
|||||||
|
|
||||||
# OIDC is the only login method left, so with auth_immediately the
|
# OIDC is the only login method left, so with auth_immediately the
|
||||||
# Log In button redirects straight to Dex. Accounts link by email.
|
# Log In button redirects straight to Dex. Accounts link by email.
|
||||||
SiteSetting.ldap_enabled = false
|
|
||||||
SiteSetting.enable_local_logins = false
|
SiteSetting.enable_local_logins = false
|
||||||
SiteSetting.auth_immediately = true
|
SiteSetting.auth_immediately = true
|
||||||
# New members sign up at www.eom.dev; Discourse must still create their
|
# New members sign up at www.eom.dev; Discourse must still create their
|
||||||
# account on first Dex login.
|
# account on first Dex login.
|
||||||
SiteSetting.allow_new_registrations = true
|
SiteSetting.allow_new_registrations = true
|
||||||
|
# Chat is bundled in core and on by default; this forum does not use it.
|
||||||
|
SiteSetting.chat_enabled = false
|
||||||
|
|
||||||
head_tag = <<~HTML
|
head_tag = <<~HTML
|
||||||
<script>
|
<script>
|
||||||
@@ -40,5 +41,5 @@ Theme.where(component: false).find_each do |theme|
|
|||||||
theme.add_relative_theme!(:child, component)
|
theme.add_relative_theme!(:child, component)
|
||||||
end
|
end
|
||||||
|
|
||||||
puts "ldap_enabled=#{SiteSetting.ldap_enabled} component=#{component.id} " \
|
puts "enable_local_logins=#{SiteSetting.enable_local_logins} component=#{component.id} " \
|
||||||
"parents=#{component.parent_themes.pluck(:name).join(', ')}"
|
"parents=#{component.parent_themes.pluck(:name).join(', ')}"
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# Discourse v2026.10.0, built with the official launcher (see web_only.yml).
|
||||||
|
# Reuses the existing Bitnami-era uploads volume, Redis and Kubernetes secrets.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: discourse-web
|
||||||
|
namespace: discourse
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: discourse-web
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: discourse-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: discourse-web
|
||||||
|
spec:
|
||||||
|
# The image is 3.77 GB and most nodes have only 3-8 GB free; pulling it
|
||||||
|
# elsewhere risks DiskPressure and evicting the MetalLB speakers.
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: alpha-worker-0
|
||||||
|
initContainers:
|
||||||
|
# The launcher image runs Discourse as uid 1000; Bitnami used 999.
|
||||||
|
- name: fix-ownership
|
||||||
|
image: gitea.eom.dev/devops/discourse-web:v2026.10.0
|
||||||
|
command: ["sh", "-c", "chown -R 1000:1000 /shared/uploads /shared/backups"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: discourse-data
|
||||||
|
mountPath: /shared/uploads
|
||||||
|
subPath: discourse/public/uploads
|
||||||
|
- name: discourse-data
|
||||||
|
mountPath: /shared/backups
|
||||||
|
subPath: discourse/public/backups
|
||||||
|
containers:
|
||||||
|
- name: discourse
|
||||||
|
image: gitea.eom.dev/devops/discourse-web:v2026.10.0
|
||||||
|
command: ["/sbin/boot"]
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
env:
|
||||||
|
- { name: RAILS_ENV, value: production }
|
||||||
|
- { name: LANG, value: en_US.UTF-8 }
|
||||||
|
- { name: LC_ALL, value: en_US.UTF-8 }
|
||||||
|
- { name: MIGRATE_ON_BOOT, value: "1" }
|
||||||
|
- { name: PRECOMPILE_ON_BOOT, value: "1" }
|
||||||
|
- { name: CREATE_DB_ON_BOOT, value: "0" }
|
||||||
|
- { name: UNICORN_WORKERS, value: "2" }
|
||||||
|
- { name: UNICORN_SIDEKIQS, value: "1" }
|
||||||
|
- { name: DISCOURSE_HOSTNAME, value: discourse.eom.dev }
|
||||||
|
- { name: DISCOURSE_DB_SOCKET, value: "" }
|
||||||
|
- { name: DISCOURSE_DB_HOST, value: discourse-postgres18 }
|
||||||
|
- { name: DISCOURSE_DB_NAME, value: bitnami_application }
|
||||||
|
- { name: DISCOURSE_DB_USERNAME, value: bn_discourse }
|
||||||
|
- name: DISCOURSE_DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: { name: discourse-postgresql, key: password }
|
||||||
|
- { name: DISCOURSE_REDIS_HOST, value: discourse-redis-master }
|
||||||
|
- name: DISCOURSE_REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: { name: discourse-redis, key: redis-password }
|
||||||
|
- { name: DISCOURSE_SMTP_ADDRESS, value: postfix.eom.dev }
|
||||||
|
- { name: DISCOURSE_SMTP_PORT, value: "587" }
|
||||||
|
- { name: DISCOURSE_SMTP_USER_NAME, value: discourse }
|
||||||
|
- name: DISCOURSE_SMTP_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef: { name: discourse-discourse, key: smtp-password }
|
||||||
|
startupProbe:
|
||||||
|
httpGet: &status
|
||||||
|
path: /srv/status
|
||||||
|
port: http
|
||||||
|
httpHeaders:
|
||||||
|
- { name: Host, value: discourse.eom.dev }
|
||||||
|
- { name: X-Forwarded-Proto, value: https }
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 180
|
||||||
|
readinessProbe:
|
||||||
|
httpGet: *status
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 6
|
||||||
|
livenessProbe:
|
||||||
|
httpGet: *status
|
||||||
|
periodSeconds: 20
|
||||||
|
failureThreshold: 6
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 1Gi
|
||||||
|
volumeMounts:
|
||||||
|
- name: shared
|
||||||
|
mountPath: /shared
|
||||||
|
- name: discourse-data
|
||||||
|
mountPath: /shared/uploads
|
||||||
|
subPath: discourse/public/uploads
|
||||||
|
- name: discourse-data
|
||||||
|
mountPath: /shared/backups
|
||||||
|
subPath: discourse/public/backups
|
||||||
|
volumes:
|
||||||
|
- name: shared
|
||||||
|
emptyDir: {}
|
||||||
|
- name: discourse-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: discourse
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# PostgreSQL 18 with pgvector for Discourse. Runs alongside the old Bitnami
|
||||||
|
# PostgreSQL 17 StatefulSet, whose volume stays untouched for rollback.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: discourse-postgres18
|
||||||
|
namespace: discourse
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: discourse-postgres18
|
||||||
|
spec:
|
||||||
|
clusterIP: None
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: discourse-postgres18
|
||||||
|
ports:
|
||||||
|
- name: postgresql
|
||||||
|
port: 5432
|
||||||
|
targetPort: postgresql
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: discourse-postgres18
|
||||||
|
namespace: discourse
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: discourse-postgres18
|
||||||
|
spec:
|
||||||
|
serviceName: discourse-postgres18
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: discourse-postgres18
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: discourse-postgres18
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgresql
|
||||||
|
image: pgvector/pgvector:pg18
|
||||||
|
ports:
|
||||||
|
- name: postgresql
|
||||||
|
containerPort: 5432
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: postgres
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: discourse-postgresql
|
||||||
|
key: postgres-password
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["pg_isready", "-U", "postgres", "-h", "127.0.0.1"]
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 6
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["pg_isready", "-U", "postgres", "-h", "127.0.0.1"]
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 6
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/postgresql
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: data
|
||||||
|
spec:
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
storageClassName: nfs-client
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 2Ti
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Build-only config for the eom.dev Discourse image. No secrets belong here:
|
||||||
|
# database, Redis and SMTP credentials are supplied at runtime by Kubernetes.
|
||||||
|
templates:
|
||||||
|
- "templates/web.template.yml"
|
||||||
|
- "templates/web.ratelimited.template.yml"
|
||||||
|
|
||||||
|
params:
|
||||||
|
version: v2026.10.0
|
||||||
|
|
||||||
|
env:
|
||||||
|
LC_ALL: en_US.UTF-8
|
||||||
|
LANG: en_US.UTF-8
|
||||||
|
LANGUAGE: en_US.UTF-8
|
||||||
|
DISCOURSE_HOSTNAME: discourse.eom.dev
|
||||||
|
DISABLE_LETSENCRYPT: 1
|
||||||
|
|
||||||
|
# Everything else the forum uses (openid-connect, reactions, math, post-voting,
|
||||||
|
# adplugin, subscriptions, chat) is bundled in core at this version.
|
||||||
|
hooks:
|
||||||
|
after_code:
|
||||||
|
- exec:
|
||||||
|
cd: $home/plugins
|
||||||
|
cmd:
|
||||||
|
- git clone https://github.com/discourse/discourse-activity-pub.git
|
||||||
|
- git clone https://github.com/discourse/discourse-prometheus.git
|
||||||
|
|
||||||
|
run:
|
||||||
|
# ingress-nginx terminates TLS, so trust its X-Forwarded-For only from the pod
|
||||||
|
# network. Without this every visitor shares the ingress IP and hits rate limits.
|
||||||
|
- file:
|
||||||
|
path: /etc/nginx/conf.d/outlets/server/real-ip.conf
|
||||||
|
chmod: 644
|
||||||
|
contents: |
|
||||||
|
set_real_ip_from 10.244.0.0/16;
|
||||||
|
real_ip_header X-Forwarded-For;
|
||||||
|
real_ip_recursive on;
|
||||||
+47
-81
@@ -1,90 +1,57 @@
|
|||||||
---
|
---
|
||||||
# tasks file for discourse
|
# tasks file for discourse
|
||||||
|
#
|
||||||
|
# Discourse runs from an image built with the official launcher; the Bitnami
|
||||||
|
# chart stops at 3.5.0. To upgrade, change params.version in
|
||||||
|
# files/discourse/web_only.yml, then from a discourse_docker checkout:
|
||||||
|
# cp <role>/files/discourse/web_only.yml containers/web_only.yml
|
||||||
|
# launcher build --set base_image=<base_image from templates/web.template.yml>-web-only \
|
||||||
|
# --tag gitea.eom.dev/devops/discourse-web:<version> web_only --output=type=docker
|
||||||
|
# Push it, update the image tag in files/discourse/discourse-web.yaml, and re-run.
|
||||||
|
#
|
||||||
|
# Pushing through the gitea ingress needs proxy-body-size raised and
|
||||||
|
# proxy-request-buffering "off" for the push, then restored. With buffering on,
|
||||||
|
# ingress-nginx writes layers to its node's small disk; on 2026-11-08 that caused
|
||||||
|
# DiskPressure, evicted the MetalLB speaker, and took the public sites down.
|
||||||
|
#
|
||||||
|
# Redis, the secrets, the uploads PVC, the Service and the Ingress still come from
|
||||||
|
# the original Helm release, which must stay installed but is no longer upgraded.
|
||||||
|
- name: Deploy PostgreSQL 18 for Discourse
|
||||||
|
kubernetes.core.k8s:
|
||||||
|
state: present
|
||||||
|
src: "{{ role_path }}/files/discourse/postgres18.yaml"
|
||||||
|
|
||||||
- name: Deploy Discourse
|
- name: Deploy Discourse
|
||||||
kubernetes.core.helm:
|
kubernetes.core.k8s:
|
||||||
name: discourse
|
state: present
|
||||||
chart_ref: bitnami/discourse
|
src: "{{ role_path }}/files/discourse/discourse-web.yaml"
|
||||||
release_namespace: discourse
|
wait: true
|
||||||
create_namespace: true
|
wait_condition:
|
||||||
values:
|
type: Available
|
||||||
auth:
|
status: "True"
|
||||||
email: discourse@eom.dev
|
wait_timeout: 1800
|
||||||
username: discourse_admin
|
|
||||||
password: "{{ discourse_admin_password }}"
|
- name: Point the Discourse Service at the launcher-built pods
|
||||||
host: "https://discourse.eom.dev"
|
kubernetes.core.k8s:
|
||||||
siteName: 'Discourse'
|
state: present
|
||||||
smtp:
|
merge_type: merge
|
||||||
enabled: true
|
definition:
|
||||||
host: "postfix.eom.dev"
|
apiVersion: v1
|
||||||
port: "587"
|
kind: Service
|
||||||
protocol: "tls"
|
metadata:
|
||||||
user: "discourse"
|
name: discourse
|
||||||
password: "{{ discourse_admin_password }}"
|
namespace: discourse
|
||||||
image:
|
spec:
|
||||||
debug: false
|
selector:
|
||||||
repository: bitnamilegacy/discourse
|
app.kubernetes.io/name: discourse-web
|
||||||
tag: 3.4.7-debian-12-r0
|
app.kubernetes.io/instance: null
|
||||||
service:
|
|
||||||
externalTrafficPolicy: Local
|
|
||||||
discourse:
|
|
||||||
skipInstall: false
|
|
||||||
plugins:
|
|
||||||
- https://github.com/discourse/discourse-adplugin
|
|
||||||
- https://github.com/discourse/discourse-subscriptions
|
|
||||||
- https://github.com/discourse/discourse-activity-pub
|
|
||||||
- https://github.com/discourse/discourse-openid-connect
|
|
||||||
# discourse-ldap-auth removed: its login cannot be disabled by setting,
|
|
||||||
# and sign-in goes through Dex (discourse-openid-connect) only.
|
|
||||||
- https://github.com/discourse/discourse-math
|
|
||||||
- https://github.com/discourse/discourse-post-voting
|
|
||||||
- https://github.com/discourse/discourse-prometheus
|
|
||||||
- https://github.com/discourse/discourse-reactions
|
|
||||||
command:
|
|
||||||
- /bin/bash
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
rm -r /opt/bitnami/discourse/plugins/chat
|
|
||||||
chown -R discourse:root /opt/bitnami/discourse/plugins
|
|
||||||
/opt/bitnami/scripts/discourse/entrypoint.sh /opt/bitnami/scripts/discourse/run.sh
|
|
||||||
persistPlugins: false
|
|
||||||
compatiblePlugins: true
|
|
||||||
resourcesPreset: none
|
|
||||||
persistence:
|
|
||||||
size: 2Ti
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
ingressClassName: "nginx"
|
|
||||||
hostname: discourse.eom.dev
|
|
||||||
annotations:
|
|
||||||
cert-manager.io/cluster-issuer: ca-issuer
|
|
||||||
nginx.ingress.kubernetes.io/proxy-set-header: "X-Forwarded-Proto $scheme"
|
|
||||||
tls: true
|
|
||||||
postgresql:
|
|
||||||
enabled: true
|
|
||||||
image:
|
|
||||||
repository: bitnamilegacy/postgresql
|
|
||||||
auth:
|
|
||||||
enablePostgresUser: true
|
|
||||||
postgresPassword: "{{ discourse_admin_password }}"
|
|
||||||
password: "{{ discourse_admin_password }}"
|
|
||||||
primary:
|
|
||||||
persistence:
|
|
||||||
size: 2Ti
|
|
||||||
redis:
|
|
||||||
enabled: true
|
|
||||||
image:
|
|
||||||
repository: bitnamilegacy/redis
|
|
||||||
auth:
|
|
||||||
password: "{{ discourse_admin_password }}"
|
|
||||||
|
|
||||||
- name: Find the Discourse pod
|
- name: Find the Discourse pod
|
||||||
kubernetes.core.k8s_info:
|
kubernetes.core.k8s_info:
|
||||||
kind: Pod
|
kind: Pod
|
||||||
namespace: discourse
|
namespace: discourse
|
||||||
label_selectors:
|
label_selectors:
|
||||||
- app.kubernetes.io/instance=discourse
|
- app.kubernetes.io/name=discourse-web
|
||||||
- app.kubernetes.io/name=discourse
|
|
||||||
field_selectors:
|
field_selectors:
|
||||||
- status.phase=Running
|
- status.phase=Running
|
||||||
register: discourse_pods
|
register: discourse_pods
|
||||||
@@ -103,8 +70,7 @@
|
|||||||
pod: "{{ discourse_pods.resources[0].metadata.name }}"
|
pod: "{{ discourse_pods.resources[0].metadata.name }}"
|
||||||
container: discourse
|
container: discourse
|
||||||
command: >-
|
command: >-
|
||||||
bash -c 'cd /opt/bitnami/discourse && PATH=/opt/bitnami/ruby/bin:$PATH
|
bash -c 'cd /var/www/discourse && su discourse -c
|
||||||
RAILS_ENV=production bundle exec ruby -e
|
"RAILS_ENV=production bundle exec rails runner /tmp/discourse-sso.rb"'
|
||||||
"require \"/opt/bitnami/discourse/config/environment\"; load \"/tmp/discourse-sso.rb\""'
|
|
||||||
register: discourse_sso
|
register: discourse_sso
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|||||||
Reference in New Issue
Block a user