7 Commits

Author SHA1 Message Date
stevensting 79a43dbadc chore: publish 1.0.0+1.0.5-social-amd64 release 2026-07-08 12:35:31 +02:00
stevensting 466ff8e447 add release label 2026-07-08 12:17:40 +02:00
stevensting d74f05c0d1 Prepare recipe release (#14)
Reviewed-on: #14
Co-authored-by: stevensting <stefan@klasse-methode.it>
Co-committed-by: stevensting <stefan@klasse-methode.it>
2026-07-08 10:12:53 +00:00
stevensting df3229884d Merge pull request 'add basicauth service' (#16) from basicauth into main
Reviewed-on: #16
2026-07-07 07:55:55 +00:00
mayel 87f9209cd5 db tuning 2026-07-04 08:47:38 +01:00
mayel 8c4d9cce46 memory limits in pg tuner 2026-07-04 00:29:52 +01:00
mayel 5a183a0579 add stop_grace_period + a couple new env for db perf 2026-07-04 00:16:14 +01:00
24 changed files with 673 additions and 339 deletions
+138 -109
View File
@@ -1,45 +1,13 @@
TYPE=bonfire
# choose what flavour of Bonfire to run
FLAVOUR=social
APP_VERSION=latest
LETS_ENCRYPT_ENV=production
# choose what extra services you want to run
COMPOSE_FILE="compose.yml:compose.meilisearch.yml"
# To use Sonic instead of Meilisearch as the search backend:
# COMPOSE_FILE="compose.yml:compose.sonic.yml"
# Enable `compose.mail.yml` to set secrets for sending emails (remember to also include your chosen search backend's compose file):
# COMPOSE_FILE="compose.yml:compose.mail.yml"
# Enable `compose.postgres.tune.yml` for Postgres tuning (can only be enabled *after* your instance already is deployed and working) (and remember to also include your chosen search backend and mailer's compose files)
# COMPOSE_FILE="compose.yml:compose.postgres.tune.yml"
APP_VERSION_FLAVOUR=${APP_VERSION}-${FLAVOUR}
# Different flavours/forks or architectures may require different builds of bonfire:
# for ARM (manual build):
# APP_DOCKER_IMAGE=bonfirenetworks/bonfire:${APP_VERSION_FLAVOUR}-aarch64
# for x86 (built by CI):
APP_DOCKER_IMAGE=bonfirenetworks/bonfire:${APP_VERSION_FLAVOUR}-amd64
# multi-arch image (built by CI, but currently not working):
#APP_DOCKER_IMAGE=bonfirenetworks/bonfire:${APP_VERSION_FLAVOUR}
DB_DOCKER_VERSION=17-3.5
# note that different flavours or architectures may require different postgres builds:
# For ARM or x86:
DB_DOCKER_IMAGE=ghcr.io/baosystems/postgis:${DB_DOCKER_VERSION}
# for x86:
# DB_DOCKER_IMAGE=postgis/postgis:${DB_DOCKER_VERSION}-alpine
# multiarch (but doesn't have required Postgis extension)
#DB_DOCKER_IMAGE=postgres:${DB_DOCKER_VERSION}-alpine
# TODO: maybe to use for Upgrading to a new Postgres version? (NOTE: does not work with postgis data)
# DB_DOCKER_IMAGE=pgautoupgrade/pgautoupgrade:16-alpine
## BASIC_AUTH
## Use 'echo $(htpasswd -nB username)' to generate the secret and store it in secret 'usersfile', multiple user/hashedpassword combinations can be added as comma separated list
#COMPOSE_FILE="$COMPOSE_FILE:compose.basicauth.yml"
#SECRET_USERSFILE_VERSION=v1
# choose what flavour of Bonfire to run. default: social
#APP_FLAVOUR=social
# uncomment to run specific version e.g. 1.0.4 or latest release branch. available: latest, latest-rc, latest-beta, latest-alpha
#APP_VERSION=latest
# choose specific build. default: amd64, available: aarch64
#APP_PLATFORM=aarch64
# enter your instance's domain name
DOMAIN=bonfire.example.com
@@ -50,42 +18,72 @@ DOMAIN=bonfire.example.com
# enable abra backups
ENABLE_BACKUPS=true
COMPOSE_FILE="compose.yml"
# uncomment in order to NOT automatically change the database schema when you upgrade the app
# DISABLE_DB_AUTOMIGRATION=true
# ====================================
# DATABASE CONFIG
# max file upload size - default is 20 meg
UPLOAD_LIMIT=20000000
COMPOSE_FILE="$COMPOSE_FILE:compose.postgres.yml"
SECRET_POSTGRES_PASSWORD_VERSION=v1
# upper limit for how much RAM you want the DB to use, in megabytes (the same amount of swap will also be allocated to the DB container)
DB_MEMORY_LIMIT=5000
# upper limit for how much RAM you want the DB to use, in megabytes (the same amount of swap will also be allocated to the DB container) As a rule of thumb use 25% of system RAM.
DB_MEMORY_LIMIT=1024
#DB_MAX_CONNECTIONS=200
# set to true *after* your initial deployment to enable concurrent index creation for faster migrations in future upgrades
DB_MIGRATE_INDEXES_CONCURRENTLY=false
# how much info to include in logs (from less to more: emergency, alert, critical, error, warning, notice, info, debug)
LOG_LEVEL=info
# for manual selection of DB version and docker image uncomment
#DB_DOCKER_VERSION=17-3.5
# note that different flavours or architectures may require different postgres builds:
# For ARM or x86:
#DB_DOCKER_IMAGE=ghcr.io/baosystems/postgis
# for x86:
#DB_DOCKER_IMAGE=postgis/postgis
#DB_DOCKER_VERSION=17-3.5-alpine
# uncomment in order to NOT automatically change the database schema when you upgrade the app
# DISABLE_DB_AUTOMIGRATION=true
# Enable `compose.postgres.tune.yml` for Postgres tuning (can only be enabled *after* your instance already is deployed and working)
# COMPOSE_FILE="$COMPOSE_FILE:postgres/compose.postgres.tune.yml"
# ====================================
# SECRETS
## BASIC_AUTH
# please make sure you change everything to your own secrets!
# and do not check your env file into any public git repo
# change ALL the values:
## Use 'echo $(htpasswd -nB username)' to generate the secret and store it in secret 'usersfile', multiple user/hashedpassword combinations can be added as comma separated list
#COMPOSE_FILE="$COMPOSE_FILE:compose.basicauth.yml"
#SECRET_USERSFILE_VERSION=v1
# ====================================
# SEARCH BACKEND
# recommended search backend sonic
#COMPOSE_FILE="$COMPOSE_FILE:compose.sonic.yml"
# docker secret cannot be used due to distroless image, threfore add secret here
#SONIC_PASSWORD=
# alternative search service
#COMPOSE_FILE="$COMPOSE_FILE:compose.meilisearch.yml"
#SECRET_MEILI_MASTER_KEY_VERSION=v1
# ====================================
# MAIL
# COMPOSE_FILE="$COMPOSE_FILE:compose.mail.yml"
# SECRET_MAIL_KEY_VERSION=v1
# private key only necessary for some mail provider
# COMPOSE_FILE="$COMPOSE_FILE:compose.mail.privatekey.yml"
# SECRET_MAIL_PRIVATE_KEY_VERSION=v1
# what service to use for sending out emails (eg. smtp, mailgun, none) NOTE: you should also set the corresponding keys below for the relevant service you choose, and uncomment the COMPOSE_FILE line for the relevant service if needed
MAIL_BACKEND=none
#COMPOSE_FILE="$COMPOSE_FILE:compose.mail.yml"
#SECRET_MAIL_PASSWORD_VERSION=v1
#MAIL_BACKEND=none
# signup to an email service and edit with relevant info, see: https://docs.bonfirenetworks.org/Bonfire.Mailer.html
# MAIL_DOMAIN=mgo.example.com
# MAIL_KEY=xyz
# NOTE: please add `compose.mail.yml` to COMPOSE_FILE above and use the abra secret instead of env for secrets ^
# MAIL_FROM=admin@example.com
# MAIL_PROJECT_ID=
# MAIL_PRIVATE_KEY=
# MAIL_BASE_URI=
# MAIL_REGION=
# MAIL_SESSION_TOKEN=
@@ -98,94 +96,125 @@ MAIL_BACKEND=none
# MAIL_RETRIES=
# MAIL_ARGS=
# Store uploads in S3-compatible service:
# UPLOADS_S3_BUCKET=
# UPLOADS_S3_ACCESS_KEY_ID=
# UPLOADS_S3_SECRET_ACCESS_KEY=
# TODO: please use the abra secret instead of env for secrets ^
# UPLOADS_S3_REGION=fr-par
# UPLOADS_S3_HOST=s3.fr-par.scw.cloud
# UPLOADS_S3_SCHEME=https://
# UPLOADS_S3_URL=
# UPLOADS_S3_DEFAULT_URL=
# AWS_ROLE_ARN=
# AWS_WEB_IDENTITY_TOKEN_FILE=
# ====================================
# UPLOADS
# GHOST Integration
#GHOST_URL=https://example.ghost.io
#GHOST_CONTENT_API_KEY=
#GHOST_ADMIN_API_KEY=
#IFRAME_ALLOWED_ORIGINS=https://example.com
# max file upload size - default is 20 meg
UPLOAD_LIMIT=20000000
# Store uploads in S3-compatible service:
#COMPOSE_FILE="$COMPOSE_FILE:compose.s3.yml"
#SECRET_UPLOADS_S3_ACCESS_KEY_ID_VERSION=v1
#SECRET_UPLOADS_S3_SECRET_ACCESS_KEY_VERSION=v1
#UPLOADS_S3_BUCKET=
#UPLOADS_S3_REGION=fr-par
#UPLOADS_S3_HOST=s3.fr-par.scw.cloud
#UPLOADS_S3_SCHEME=https://
#UPLOADS_S3_URL=
#UPLOADS_S3_DEFAULT_URL=
#AWS_ROLE_ARN=
# Optionally use AWS identity token file
#COMPOSE_FILE="$COMPOSE_FILE:compose.s3.tokenfile.yml"
#SECRET_AWS_WEB_IDENTITY_TOKEN_VERSION=v1
# ====================================
# SSO
# Enable using Bonfire as an SSO provider for external apps to sign in with?
# ENABLE_SSO_PROVIDER=false
OAUTH_ISSUER=https://${DOMAIN}
#OAUTH_ISSUER=https://${DOMAIN}
# OpenID Connect: connect as a client to the OpenID Connect provider with callback url https://yourinstance.tld/oauth/client/openid_1
# OpenID Connect: connect as a client to the OpenID Connect provider with callback url https://yourinstance.tld/openid/client/openid_1
#COMPOSE_FILE="$COMPOSE_FILE:compose.auth.openid.yml"
#SECRET_OPENID_CLIENT_SECRET_VERSION=v1
# OPENID_1_DISCOVERY=
# OPENID_1_DISPLAY_NAME=
# OPENID_1_CLIENT_ID=
# OPENID_1_CLIENT_SECRET=
# OPENID_1_SCOPE=
# OPENID_1_RESPONSE_TYPE=code
# OPENID_1_ENABLE_SIGNUP=false
# ^ can be code, token or id_token
# orcid.org SSO: connect as a client to the orcid.org OpenID Connect provider with callback url https://yourinstance.tld/oauth/client/orcid
# orcid.org SSO: connect as a client to the orcid.org OpenID Connect provider with callback url https://yourinstance.tld/openid/client/orcid
#COMPOSE_FILE="$COMPOSE_FILE:compose.auth.orcid.yml"
#SECRET_ORCID_CLIENT_SECRET_VERSION=v1
# ORCID_CLIENT_ID=
# ORCID_CLIENT_SECRET=
# OAuth2 provider: connect as a client to the OAuth2 provider with callback url https://yourinstance.tld/oauth/client/oauth_1
#COMPOSE_FILE="$COMPOSE_FILE:compose.auth.oauth.yml"
# SECRET_OAUTH_CLIENT_SECRET_VERSION=v1
# OAUTH_1_DISPLAY_NAME=
# OAUTH_1_CLIENT_ID=
# OAUTH_1_CLIENT_SECRET=
# OAUTH_1_AUTHORIZE_URI=
# OAUTH_1_ACCESS_TOKEN_URI=
# OAUTH_1_USER_INFO_URI=
# OAUTH_1_ENABLE_SIGNUP=false
# github.com SSO: connect as a client to the github.com OAuth2 provider with callback url https://yourinstance.tld/oauth/client/github
#COMPOSE_FILE="$COMPOSE_FILE:compose.auth.github.yml"
# SECRET_GITHUB_CLIENT_SECRET_VERSION=v1
# GITHUB_APP_CLIENT_ID=
# GITHUB_CLIENT_SECRET=
# More Bonfire extensions configs:
# Zenodo SSO: connect as a client to the Zenodo OAuth2 provider with callback url https://yourinstance.tld/oauth/client/zenodo
# ZENODO_CLIENT_ID=
# ZENODO_CLIENT_SECRET=
# ZENODO_GRANT_TYPE=
# ZENODO_SCOPE=
# ZENODO_ENV=
# ====================================
# GHOST INTEGRATION
#COMPOSE_FILE="$COMPOSE_FILE:compose.ghost.yml"
#SECRET_GHOST_CONTENT_API_KEY_VERSION=v1
#SECRET_GHOST_ADMIN_API_KEY_VERSION=v1
#SECRET_GHOST_WEBHOOK_SECRET_VERSION=v1
#GHOST_URL=https://example.ghost.io
# ====================================
# OPEN TELEMETRY
#COMPOSE_FILE="$COMPOSE_FILE:compose.otel.yml"
#SECRET_HONEYCOMB_API_KEY_VERSION=v1
#SECRET_LIGHTSTEP_API_KEY_VERSION=v1
# ====================================
# BONFIRE EXTENSIONS AND MISC
#COMPOSE_FILE="$COMPOSE_FILE:compose.webpush.yml"
# SECRET_WEBPUSH_PRIVATE_KEY_VERSION=v1
# WEB_PUSH_SUBJECT=mailto:admin@example.com
# WEB_PUSH_PUBLIC_KEY=xyz
# WEB_PUSH_PRIVATE_KEY=abc
# GEOLOCATE_OPENCAGEDATA=
# GITHUB_TOKEN=xyz
# AKISMET_API_KEY=
WITH_LV_NATIVE=0
WITH_IMAGE_VIX=1
WITH_AI=0
LIVE_DASHBOARD_LOGGER=false
#COMPOSE_FILE="$COMPOSE_FILE:compose.github.yml"
#SECRET_GITHUB_TOKEN_VERSION=v1
#COMPOSE_FILE="$COMPOSE_FILE:compose.akismet.yml"
#SECRET_AKISMET_API_KEY_VERSION=v1
#COMPOSE_FILE="$COMPOSE_FILE:compose.mapbox.yml"
#SECRET_MAPBOX_API_KEY_VERSION=v1
# GEOLOCATE_OPENCAGEDATA=
#IFRAME_ALLOWED_ORIGINS=https://example.com
# how much info to include in app logs (from less to more: emergency, alert, critical, error, warning, notice, info, debug); default is warning — note that debug-level entries are removed from release builds entirely, so the most verbose usable level here is info
# PROD_LOG_LEVEL=warning
# how much info to include in logs (from less to more: emergency, alert, critical, error, warning, notice, info, debug)
LOG_LEVEL=info
# error reporting:
# SENTRY_DSN=
# ====================================
# these secrets will be autogenerated/managed by abra and docker"
SECRET_POSTGRES_PASSWORD_VERSION=v1
SECRET_MEILI_MASTER_KEY_VERSION=v1
SECRET_SONIC_PASSWORD_VERSION=v1
# SECRETS
# these secrets will be autogenerated/managed by abra and docker
SECRET_SEEDS_PW_VERSION=v1
SECRET_LIVEBOOK_PASSWORD_VERSION=v1
SECRET_MAIL_KEY_VERSION=v1
SECRET_SECRET_KEY_BASE_VERSION=v1 # length=128
SECRET_SIGNING_SALT_VERSION=v1 # length=128
SECRET_ENCRYPTION_SALT_VERSION=v1 # length=128
SECRET_RELEASE_COOKIE_VERSION=v1
# ====================================
# You should not have to edit any of the following ones:
APP_NAME=Bonfire
LANG=en_US.UTF-8
SEEDS_USER=root
ERLANG_COOKIE=bonfire_cookie
REPLACE_OS_VARS=true
LIVEVIEW_ENABLED=true
ACME_AGREE=true
SHOW_DEBUG_IN_DEV=true
LETS_ENCRYPT_ENV=production
HOSTNAME=$DOMAIN
#PLUG_SERVER=bandit
+1 -1
View File
@@ -1,4 +1,4 @@
export APP_ENTRYPOINT_VERSION=v3
export APP_ENTRYPOINT_VERSION=v4
export PG_BACKUP_VERSION=v6
export MEILI_BACKUP_VERSION=v4
export SONIC_CFG_VERSION=v1
+14
View File
@@ -0,0 +1,14 @@
version: "3.8"
services:
app:
environment:
- AKISMET_API_KEY_FILE=/run/secrets/akismet_api_key
secrets:
- akismet_api_key
secrets:
akismet_api_key:
external: true
name: ${STACK_NAME}_akismet_api_key_${SECRET_AKISMET_API_KEY_VERSION:-v1}
+14
View File
@@ -0,0 +1,14 @@
version: "3.8"
services:
app:
environment:
- GITHUB_APP_CLIENT_ID
- GITHUB_CLIENT_SECRET_FILE=/run/secrets/github_client_secret
secrets:
- github_client_secret
secrets:
oauth_client_secret:
external: true
name: ${STACK_NAME}_github_client_secret_${SECRET_GITHUB_CLIENT_SECRET_VERSION:-v1}
+19
View File
@@ -0,0 +1,19 @@
version: "3.8"
services:
app:
environment:
- OAUTH_1_DISPLAY_NAME
- OAUTH_1_CLIENT_ID
- OAUTH_1_CLIENT_SECRET_FILE=/run/secrets/oauth_client_secret
- OAUTH_1_AUTHORIZE_URI
- OAUTH_1_ACCESS_TOKEN_URI
- OAUTH_1_USER_INFO_URI
- OAUTH_1_ENABLE_SIGNUP
secrets:
- oauth_client_secret
secrets:
oauth_client_secret:
external: true
name: ${STACK_NAME}_oauth_client_secret_${SECRET_OAUTH_CLIENT_SECRET_VERSION:-v1}
+19
View File
@@ -0,0 +1,19 @@
version: "3.8"
services:
app:
environment:
- OPENID_1_DISPLAY_NAME
- OPENID_1_DISCOVERY
- OPENID_1_CLIENT_ID
- OPENID_1_CLIENT_SECRET_FILE=/run/secrets/openid_client_secret
- OPENID_1_SCOPE
- OPENID_1_RESPONSE_TYPE
- OPENID_1_ENABLE_SIGNUP
secrets:
- openid_client_secret
secrets:
openid_client_secret:
external: true
name: ${STACK_NAME}_openid_client_secret_${SECRET_OPENID_CLIENT_SECRET_VERSION:-v1}
+14
View File
@@ -0,0 +1,14 @@
version: "3.8"
services:
app:
environment:
- ORCID_CLIENT_ID
- ORCID_CLIENT_SECRET_FILE=/run/secrets/orchid_client_secret
secrets:
- orchid_client_secret
secrets:
orchid_client_secret:
external: true
name: ${STACK_NAME}_orchid_client_secret_${SECRET_ORCID_CLIENT_SECRET_VERSION:-v1}
+24
View File
@@ -0,0 +1,24 @@
version: "3.8"
services:
app:
environment:
- GHOST_URL
- GHOST_CONTENT_API_KEY_FILE=/run/secrets/ghost_content_api_key
- GHOST_ADMIN_API_KEY_FILE=/run/secrets/ghost_admin_api_key
- GHOST_WEBHOOK_SECRET_FILE=/run/secrets/ghost_webhook_secret
secrets:
- ghost_content_api_key
- ghost_admin_api_key
- ghost_webhook_secret
secrets:
ghost_content_api_key:
external: true
name: ${STACK_NAME}_ghost_content_api_key_${SECRET_GHOST_CONTENT_API_KEY_VERSION:-v1}
ghost_admin_api_key:
external: true
name: ${STACK_NAME}_ghost_admin_api_key_${SECRET_GHOST_ADMIN_API_KEY_VERSION:-v1}
ghost_webhook_secret:
external: true
name: ${STACK_NAME}_ghost_webhook_secret_${SECRET_GHOST_WEBHOOK_SECRET_VERSION:-v1}
+14
View File
@@ -0,0 +1,14 @@
version: "3.8"
services:
app:
environment:
- GITHUB_TOKEN_FILE=/run/secrets/github_token
secrets:
- github_token
secrets:
github_token:
external: true
name: ${STACK_NAME}_github_token_${SECRET_GITHUB_TOKEN_VERSION:-v1}
+13
View File
@@ -0,0 +1,13 @@
version: "3.8"
services:
app:
environment:
- MAIL_PRIVATE_KEY_FILE=/run/secrets/mail_private_key
secrets:
- mail_private_key
secrets:
mail_private_key:
external: true
name: ${STACK_NAME}_mail_private_key_${SECRET_MAIL_PRIVATE_KEY_VERSION:-v1}
+16 -4
View File
@@ -2,14 +2,26 @@ version: "3.8"
services:
app:
environment:
- MAIL_BACKEND=${MAIL_BACKEND:-none}
- MAIL_DOMAIN
- MAIL_FROM
- MAIL_KEY_FILE=/run/secrets/mail_key
- MAIL_PROJECT_ID
- MAIL_BASE_URI
- MAIL_REGION
- MAIL_SERVER
- MAIL_USER
- MAIL_PORT
- MAIL_TLS
- MAIL_SSL
- MAIL_SMTP_AUTH
- MAIL_RETRIES
- MAIL_ARGS
secrets:
- mail_password
- mail_key
secrets:
mail_password:
external: true
name: ${STACK_NAME}_mail_password_${SECRET_MAIL_PASSWORD_VERSION:-v1}
mail_key:
external: true
name: ${STACK_NAME}_mail_key_${SECRET_MAIL_KEY_VERSION:-v1}
+14
View File
@@ -0,0 +1,14 @@
version: "3.8"
services:
app:
environment:
- MAPBOX_API_KEY_FILE=/run/secrets/mapbox_api_key
secrets:
- mapbox_api_key
secrets:
mapbox_api_key:
external: true
name: ${STACK_NAME}_mapbox_api_key_${SECRET_MAPBOX_API_KEY_VERSION:-v1}
+7
View File
@@ -7,6 +7,8 @@ services:
- search
environment:
- SEARCH_MEILI_INSTANCE=http://${STACK_NAME}_search:7700
secrets:
- meili_master_key
search:
image: getmeili/meilisearch:v1.14 # WIP: upgrade from v1.11 to 1.14
@@ -42,3 +44,8 @@ configs:
meili_backup:
name: ${STACK_NAME}_meili_backup_${MEILI_BACKUP_VERSION:-v4}
file: meili_backup.sh
secrets:
meili_master_key:
external: true
name: ${STACK_NAME}_meili_master_key_${SECRET_MEILI_MASTER_KEY_VERSION:-v1}
+21
View File
@@ -0,0 +1,21 @@
version: "3.8"
services:
app:
environment:
- OTEL_ENABLED
- OTEL_SERVICE_NAME
- OTEL_HONEYCOMB_API_KEY_FILE=/run/secrets/honeycomb_api_key
- OTEL_LIGHTSTEP_API_KEY_FILE=/run/secrets/lightstep_api_key
secrets:
- honeycomb_api_key
- lightstep_api_key
secrets:
honeycomb_api_key:
external: true
name: ${STACK_NAME}_honeycomb_api_key_${SECRET_HONEYCOMB_API_KEY_VERSION:-v1}
lightstep_api_key:
external: true
name: ${STACK_NAME}_lightstep_api_key_${SECRET_LIGHTSTEP_API_KEY_VERSION:-v1}
+93
View File
@@ -0,0 +1,93 @@
version: '3.8'
x-postgres-env: &postgres-env
POSTGRES_DB: bonfire_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
services:
app:
environment:
<<: *postgres-env
POSTGRES_HOST: ${STACK_NAME}_db
secrets:
- postgres_password
db:
image: ${DB_DOCKER_IMAGE:-ghcr.io/baosystems/postgis}:${DB_DOCKER_VERSION:-17-3.5}
# give Postgres time to shut down cleanly: the Swarm default (10s) force-kills it mid-shutdown on every redeploy, which wipes the cumulative stats (pg_stat_*) that autovacuum's triggers depend on — a root cause of autovacuum never running on big tables
stop_grace_period: 2m # NOTE: abra validates the schema before env interpolation, so this duration field can't be env-parameterized
# static tuning defaults for deployments NOT using the postgres tuner overlay (NOTE: `compose.postgres.tune.yml` computes these from your system resources instead of hardcoding them, so please use it!)
# Notes:
# memory defaults are deliberately SAFE-SMALL (fit a 1 or 2GB VPS with the app co-hosted): shared_buffers is allocated at boot and maintenance_work_mem is per vacuum worker, so oversizing can prevent startup or OOM small machines — the tuner overlay right-sizes them instead (~25% / ~6% of the DB's RAM budget);
# max_connections is deliberately low because Bonfire's Ecto pool is the connection pooler (~25-50 conns; oversizing shrinks usable work_mem);
# jit off = measured per-query compile tax on Bonfire's many-join queries with no benefit;
# the autovacuum settings suit Bonfire's soft-delete/append workload where default thresholds never trigger on big tables.
command: >
postgres
-c max_connections=${PG_MAX_CONNECTIONS:-100}
-c shared_buffers=${PG_SHARED_BUFFERS_MB:-256}MB
-c effective_cache_size=${PG_EFFECTIVE_CACHE_SIZE_MB:-768}MB
-c work_mem=${PG_WORK_MEM_MB:-16}MB
-c maintenance_work_mem=${PG_MAINTENANCE_WORK_MEM_MB:-128}MB
-c random_page_cost=${PG_RANDOM_PAGE_COST:-1.1}
-c effective_io_concurrency=${PG_EFFECTIVE_IO_CONCURRENCY:-200}
-c jit=${PG_JIT:-off}
-c wal_compression=${PG_WAL_COMPRESSION:-lz4}
-c default_toast_compression=${PG_TOAST_COMPRESSION:-lz4}
-c shared_preload_libraries=${PG_PRELOAD_LIBS:-pg_stat_statements}
-c pg_stat_statements.track=all
-c track_io_timing=on
-c track_activity_query_size=16384
-c autovacuum_vacuum_scale_factor=0.05
-c autovacuum_vacuum_insert_scale_factor=0.05
-c autovacuum_vacuum_cost_limit=1000
-c log_autovacuum_min_duration=0
-c log_min_duration_statement=${PG_LOG_MIN_DURATION_STATEMENT:-2000}
-c log_lock_waits=on
-c log_temp_files=0
# -c statement_timeout=1800000
#entrypoint: ['tail', '-f', '/dev/null'] # uncomment when the Postgres DB is corrupted and won't start
volumes:
- db-data:/var/lib/postgresql/data
- type: tmpfs
target: /dev/shm
tmpfs:
size: 1000000000
# about 1 GB in bytes
tmpfs:
- /tmp:size=${DB_MEMORY_LIMIT:-1024}M^
networks:
- internal
environment:
<<: *postgres-env
secrets:
- postgres_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U $$POSTGRES_USER"]
interval: 10s
timeout: 5s
retries: 5
deploy:
labels:
backupbot.backup: ${ENABLE_BACKUPS:-true}
backupbot.backup.pre-hook: "/pg_backup.sh backup"
backupbot.backup.volumes.db-data.path: "backup.sql"
backupbot.restore.post-hook: '/pg_backup.sh restore'
configs:
- source: pg_backup
target: /pg_backup.sh
mode: 0555
volumes:
db-data:
configs:
pg_backup:
name: ${STACK_NAME}_pg_backup_${PG_BACKUP_VERSION}
file: pg_backup.sh
secrets:
postgres_password:
external: true
name: ${STACK_NAME}_postgres_password_${SECRET_POSTGRES_PASSWORD_VERSION:-v1}
+14
View File
@@ -0,0 +1,14 @@
version: "3.8"
services:
app:
environment:
- AWS_WEB_IDENTITY_TOKEN_FILE=/run/secrets/aws_web_identity_token
secrets:
- aws_web_identity_token
secrets:
aws_web_identity_token:
external: true
name: ${STACK_NAME}_aws_web_identity_token_${SECRET_AWS_WEB_IDENTITY_TOKEN_VERSION:-v1}
+31
View File
@@ -0,0 +1,31 @@
version: "3.8"
services:
app:
environment:
- UPLOADS_S3_BUCKET
- UPLOADS_S3_ACCESS_KEY_ID_FILE=/run/secrets/uploads_s3_access_key_id
- UPLOADS_S3_SECRET_ACCESS_KEY_FILE=/run/secrets/uploads_s3_secret_access_key
- UPLOADS_S3_REGION
- UPLOADS_S3_HOST
- UPLOADS_S3_SCHEME
- UPLOADS_S3_URL
- UPLOADS_S3_DEFAULT_URL
- UPLOADS_S3_URL_EXPIRATION_TTL
- AWS_ROLE_ARN
secrets:
- mail_key
- uploads_s3_access_key_id
- uploads_s3_secret_access_key
secrets:
mail_key:
external: true
name: ${STACK_NAME}_mail_key_${SECRET_MAIL_KEY_VERSION:-v1}
uploads_s3_access_key_id:
external: true
name: ${STACK_NAME}_uploads_s3_access_key_id_${SECRET_UPLOADS_S3_ACCESS_KEY_ID_VERSION:-v1}
uploads_s3_secret_access_key:
external: true
name: ${STACK_NAME}_uploads_s3_secret_access_key_${SECRET_UPLOADS_S3_SECRET_ACCESS_KEY_VERSION:-v1}
+15
View File
@@ -0,0 +1,15 @@
version: "3.8"
services:
app:
environment:
- WEB_PUSH_SUBJECT
- WEB_PUSH_PUBLIC_KEY
- WEB_PUSH_PRIVATE_KEY_FILE=/run/secrets/webpush_private_key
secrets:
- webpush_private_key
secrets:
webpush_private_key:
external: true
name: ${STACK_NAME}_webpush_private_key_${SECRET_WEBPUSH_PRIVATE_KEY_VERSION:-v1}
+39 -151
View File
@@ -3,7 +3,7 @@ version: "3.8"
services:
app:
image: ${APP_DOCKER_IMAGE}
image: ${CONTAINER_REGISTRY:-bonfirenetworks/bonfire}:${APP_VERSION:-1.0.5}-${APP_FLAVOUR:-social}-${APP_PLATFORM:-amd64}
logging:
driver: "json-file"
options:
@@ -12,119 +12,59 @@ services:
depends_on:
- db
environment:
- POSTGRES_HOST=${STACK_NAME}_db
- POSTGRES_USER=postgres
- POSTGRES_DB=bonfire_db
- PUBLIC_PORT=443
- MIX_ENV=prod
- HOSTNAME
- HOSTNAME=${DOMAIN}
- INSTANCE_DESCRIPTION
- DISABLE_DB_AUTOMIGRATION
- UPLOAD_LIMIT
- INVITE_KEY
- LANG
- SEEDS_USER
- ERLANG_COOKIE
- REPLACE_OS_VARS
- LIVEVIEW_ENABLED
- APP_NAME
- LANG=${LANG:-en_US.UTF-8}
- SEEDS_USER=${SEEDS_USER:-root}
- REPLACE_OS_VARS=${REPLACE_OS_VARS:-true}
- LIVEVIEW_ENABLED=${LIVEVIEW_ENABLED:-true}
- APP_NAME=${APP_NAME:-Bonfire}
- PLUG_SERVER
- WITH_LV_NATIVE
- WITH_IMAGE_VIX
- WITH_AI
- LIVE_DASHBOARD_LOGGER
- WITH_LV_NATIVE=${WITH_LV_NATIVE:-0}
- WITH_IMAGE_VIX=${WITH_IMAGE_VIX:-1}
- WITH_AI=${WITH_AI:-0}
- LIVE_DASHBOARD_LOGGER=${LIVE_DASHBOARD_LOGGER:-false}
- IFRAME_ALLOWED_ORIGINS
- RELEASE_DISTRIBUTION
- RELEASE_NODE
- RELEASE_MODE
- DB_SLOW_QUERY_MS
- DB_STATEMENT_TIMEOUT
- DB_QUERY_TIMEOUT
- DB_JIT
- DB_MIGRATE_INDEXES_CONCURRENTLY
- MAIL_BACKEND
- MAIL_DOMAIN
- MAIL_FROM
- MAIL_KEY
- MAIL_PROJECT_ID
- MAIL_PRIVATE_KEY
- MAIL_BASE_URI
- MAIL_REGION
- MAIL_SERVER
- MAIL_USER
- MAIL_PASSWORD
- MAIL_PORT
- MAIL_TLS
- MAIL_SSL
- MAIL_SMTP_AUTH
- MAIL_RETRIES
- MAIL_ARGS
- PROD_LOG_LEVEL
- SENTRY_DSN
- OTEL_ENABLED
- OTEL_SERVICE_NAME
- OTEL_HONEYCOMB_API_KEY
- OTEL_LIGHTSTEP_API_KEY
- WEB_PUSH_SUBJECT
- WEB_PUSH_PUBLIC_KEY
- WEB_PUSH_PRIVATE_KEY
- AKISMET_API_KEY
- MAPBOX_API_KEY
- GEOLOCATE_OPENCAGEDATA
- GITHUB_TOKEN
- UPLOADS_S3_BUCKET
- UPLOADS_S3_ACCESS_KEY_ID
- UPLOADS_S3_SECRET_ACCESS_KEY
- UPLOADS_S3_REGION
- UPLOADS_S3_HOST
- UPLOADS_S3_SCHEME
- UPLOADS_S3_URL
- UPLOADS_S3_DEFAULT_URL
- UPLOADS_S3_URL_EXPIRATION_TTL
- AWS_ROLE_ARN
- AWS_WEB_IDENTITY_TOKEN_FILE
- ENABLE_SSO_PROVIDER
- OAUTH_ISSUER
- OPENID_1_DISPLAY_NAME
- OPENID_1_DISCOVERY
- OPENID_1_CLIENT_ID
- OPENID_1_CLIENT_SECRET
- OPENID_1_SCOPE
- OPENID_1_RESPONSE_TYPE
- OPENID_1_ENABLE_SIGNUP
- OAUTH_1_DISPLAY_NAME
- OAUTH_1_CLIENT_ID
- OAUTH_1_CLIENT_SECRET
- OAUTH_1_AUTHORIZE_URI
- OAUTH_1_ACCESS_TOKEN_URI
- OAUTH_1_USER_INFO_URI
- OAUTH_1_ENABLE_SIGNUP
- GITHUB_APP_CLIENT_ID
- GITHUB_CLIENT_SECRET
- SECRET_KEY_BASE_FILE=/run/secrets/secret_key_base
- SIGNING_SALT_FILE=/run/secrets/signing_salt
- ENCRYPTION_SALT_FILE=/run/secrets/encryption_salt
- SEEDS_PW_FILE=/run/secrets/seeds_pw
- LIVEBOOK_PASSWORD_FILE=/run/secrets/livebook_password
- RELEASE_COOKIE_FILE=/run/secrets/release_cookie
- ORCID_CLIENT_ID
- ORCID_CLIENT_SECRET
- GHOST_URL
- GHOST_CONTENT_API_KEY
- GHOST_ADMIN_API_KEY
- IFRAME_ALLOWED_ORIGINS
secrets:
- postgres_password
- secret_key_base
- signing_salt
- encryption_salt
- meili_master_key
- seeds_pw
- livebook_password
- release_cookie
volumes:
- upload-data:/opt/app/data/uploads
# - backup-data:/opt/app/data/backup
networks:
- proxy
- internal
@@ -137,71 +77,25 @@ services:
restart_policy:
condition: on-failure
labels:
# how long abra waits for the deploy to converge, as needs headroom for DB migrations on upgrades
# and the db service's 2m stop_grace_period during redeploys
- "coop-cloud.${STACK_NAME}.timeout=${TIMEOUT:-300}"
- "backupbot.backup=${ENABLE_BACKUPS:-true}"
#- backupbot.backup.volumes.upload-data: "true"
#- backupbot.backup.volumes.upload-data.path: "/opt/app/data/uploads"
- "traefik.enable=true"
- "traefik.http.services.${STACK_NAME}.loadbalancer.server.port=4000"
- "traefik.http.routers.${STACK_NAME}.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS})"
- "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}"
#- "traefik.http.routers.${STACK_NAME}.middlewares=error-pages-middleware"
#- "traefik.http.services.${STACK_NAME}.loadbalancer.server.port=80"
#- "traefik.http.routers.${STACK_NAME}.middlewares=${STACK_NAME}-redirect"
#- "traefik.http.middlewares.${STACK_NAME}-redirect.headers.SSLForceHost=true"
#- "traefik.http.middlewares.${STACK_NAME}-redirect.headers.SSLHost=${DOMAIN}"
# healthcheck:
# test: ["CMD", "curl", "-f", "http://localhost"]
# interval: 30s
# timeout: 10s
# retries: 10
# start_period: 1m
db:
image: ${DB_DOCKER_IMAGE}
environment:
# - POSTGRES_PASSWORD
- POSTGRES_USER=postgres
- POSTGRES_DB=bonfire_db
- POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
secrets:
- postgres_password
volumes:
- db-data:/var/lib/postgresql/data
- type: tmpfs
target: /dev/shm
tmpfs:
size: 1000000000
# about 1 GB in bytes ^
networks:
- internal
# shm_size: ${DB_MEMORY_LIMIT} # unsupported by docker swarm
tmpfs:
- /tmp:size=${DB_MEMORY_LIMIT}M
command: >
postgres
-c max_connections=200
-c shared_buffers=${DB_MEMORY_LIMIT}MB
# -c shared_preload_libraries='pg_stat_statements'
# -c statement_timeout=1800000
# -c pg_stat_statements.track=all
#entrypoint: ['tail', '-f', '/dev/null'] # uncomment when the Postgres DB is corrupted and won't start
deploy:
labels:
backupbot.backup: ${ENABLE_BACKUPS:-true}
# backupbot.backup.volumes.db-data: false
backupbot.backup.volumes.db-data.path: "backup.sql"
backupbot.backup.pre-hook: "/pg_backup.sh backup"
backupbot.restore.post-hook: '/pg_backup.sh restore'
configs:
- source: pg_backup
target: /pg_backup.sh
mode: 0555
- "coop-cloud.${STACK_NAME}.version=1.0.0+1.0.5-social-amd64"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000"]
interval: 30s
timeout: 10s
retries: 10
start_period: 15s
volumes:
db-data:
upload-data:
# backup-data:
networks:
proxy:
@@ -213,14 +107,8 @@ configs:
name: ${STACK_NAME}_app_entrypoint_${APP_ENTRYPOINT_VERSION:-v3}
file: entrypoint.sh.tmpl
template_driver: golang
pg_backup:
name: ${STACK_NAME}_pg_backup_${PG_BACKUP_VERSION:-v4}
file: pg_backup.sh
secrets:
postgres_password:
external: true
name: ${STACK_NAME}_postgres_password_${SECRET_POSTGRES_PASSWORD_VERSION:-v1}
secret_key_base:
external: true
name: ${STACK_NAME}_secret_key_base_${SECRET_SECRET_KEY_BASE_VERSION:-v1}
@@ -230,12 +118,12 @@ secrets:
encryption_salt:
external: true
name: ${STACK_NAME}_encryption_salt_${SECRET_ENCRYPTION_SALT_VERSION:-v1}
meili_master_key:
external: true
name: ${STACK_NAME}_meili_master_key_${SECRET_MEILI_MASTER_KEY_VERSION:-v1}
seeds_pw:
external: true
name: ${STACK_NAME}_seeds_pw_${SECRET_SEEDS_PW_VERSION:-v1}
livebook_password:
external: true
name: ${STACK_NAME}_livebook_password_${SECRET_LIVEBOOK_PASSWORD_VERSION:-v1}
release_cookie:
external: true
name: ${STACK_NAME}_release_cookie_${SECRET_RELEASE_COOKIE_VERSION:-v1}
+3 -17
View File
@@ -1,22 +1,8 @@
#!/bin/sh
# put secrets from files into env
export MEILI_MASTER_KEY=$(cat /run/secrets/meili_master_key)
export POSTGRES_PASSWORD=$(cat /run/secrets/postgres_password)
export SECRET_KEY_BASE=$(cat /run/secrets/secret_key_base)
export SIGNING_SALT=$(cat /run/secrets/signing_salt)
export ENCRYPTION_SALT=$(cat /run/secrets/encryption_salt)
export SEEDS_PW=$(cat /run/secrets/seeds_pw)
export LIVEBOOK_PASSWORD=$(cat /run/secrets/livebook_password)
# Only read the secret when the MAIL_PASSWORD was not set to remain backwards compatible
if [ -f /run/secrets/mail_password ] && [ -z "${MAIL_PASSWORD}" ]; then
export MAIL_PASSWORD=$(cat /run/secrets/mail_password)
fi
# Only read the secret when the MAIL_KEY was not set to remain backwards compatible
if [ -f /run/secrets/mail_key ] && [ -z "${MAIL_KEY}" ]; then
export MAIL_KEY=$(cat /run/secrets/mail_key)
# meilisearch does not support MEILI_MASTER_KEY_FILE, so we export it here for the search service
if [ -f /run/secrets/meili_master_key ]; then
export MEILI_MASTER_KEY=$(cat /run/secrets/meili_master_key)
fi
echo "....Secrets have been loaded, now run $@...."
+4 -2
View File
@@ -15,6 +15,8 @@ services:
# # -c shared_preload_libraries='pg_stat_statements'
# # -c statement_timeout=1800000
# # -c pg_stat_statements.track=all
# give Postgres time to shut down cleanly: the Swarm default (10s) force-kills it mid-shutdown on every redeploy, which wipes the cumulative stats (pg_stat_*) that autovacuum's triggers depend on — a root cause of autovacuum never running on big tables
stop_grace_period: 2m # NOTE: abra validates the schema before env interpolation, so this duration field can't be env-parameterized
# shm_size: ${DB_MEMORY_LIMIT} # unsupported by docker swarm
tmpfs:
- /tmp:size=${DB_MEMORY_LIMIT}M
@@ -40,10 +42,10 @@ configs:
name: ${STACK_NAME}_postgres_entrypoint_${POSTGRES_ENTRYPOINT_VERSION:-v6}
file: postgres/postgres-entrypoint.sh
postgres_tune:
name: ${STACK_NAME}_postgres_tune_${POSTGRES_TUNE_VERSION:-v1}
name: ${STACK_NAME}_postgres_tune_${POSTGRES_TUNE_VERSION:-v2}
file: postgres/postgres-tune.sh
postgres_conf_tmpl:
name: ${STACK_NAME}_postgres_conf_tmpl_${POSTGRES_CONF_TMPL_VERSION:-v2}
name: ${STACK_NAME}_postgres_conf_tmpl_${POSTGRES_CONF_TMPL_VERSION:-v3}
file: postgres/postgres.conf.tmpl
bash_envsubst:
name: ${STACK_NAME}_bash_envsubst_${BASH_ENVSUBST_VERSION:-v1}
+129 -55
View File
@@ -25,7 +25,12 @@ readonly STORAGE_HDD="hdd"
readonly STORAGE_SAN="san"
# Auto-detect or use environment variables
PG_DB_TYPE="${PG_DB_TYPE:-${DB_TYPE:-oltp}}"
# NOTE: default profile is `mixed` (not `oltp`): Bonfire is OLTP plus a tail of analytics-shaped
# queries (many-join boundarised feeds/threads), which is exactly PGTune's mixed workload. It also
# right-sizes max_connections (100 vs oltp's 300 — Bonfire's Ecto pool is the pooler, ~25-50 conns;
# an oversized value wastes RAM headroom and shrinks the computed work_mem, which caused measured
# multi-TB temp-file spills in prod).
PG_DB_TYPE="${PG_DB_TYPE:-${DB_TYPE:-mixed}}"
PG_STORAGE_TYPE="${PG_STORAGE_TYPE:-${DISK_STORAGE_TYPE:-ssd}}"
PG_DB_VERSION="${PG_DB_VERSION:-17}"
@@ -35,14 +40,21 @@ detect_total_memory_kb() {
echo $((AVAILABLE_RAM_GB * GB / KB))
elif [[ -n "${AVAILABLE_RAM_MB:-}" ]]; then
echo $((AVAILABLE_RAM_MB * MB / KB))
elif [[ -r /sys/fs/cgroup/memory.max && "$(cat /sys/fs/cgroup/memory.max)" != "max" ]]; then
# cgroup v2: an explicit container memory limit is the deliberate budget signal
echo $(($(cat /sys/fs/cgroup/memory.max) / KB))
elif [[ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ]] &&
[[ "$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)" -lt $((1 << 60)) ]]; then
# cgroup v1 (its "no limit" sentinel is a huge number rather than "max")
echo $(($(cat /sys/fs/cgroup/memory/memory.limit_in_bytes) / KB))
elif [[ -f /proc/meminfo ]]; then
# Linux
awk '/MemTotal/ {print int($2)}' /proc/meminfo
# Calculate based on total available memory: claiming a fraction as co-hosted services need the rest. Sized for our standard stack (app + postgres + search + proxy). Override with PG_RAM_PERCENT or AVAILABLE_RAM_MB.
awk -v pct="${PG_RAM_PERCENT:-40}" '/MemTotal/ {print int($2 * pct / 100)}' /proc/meminfo
elif command -v sysctl &> /dev/null; then
# macOS/BSD
local mem_bytes
mem_bytes=$(sysctl -n hw.memsize 2>/dev/null || sysctl -n hw.physmem 2>/dev/null)
echo $((mem_bytes / KB))
echo $((mem_bytes * ${PG_RAM_PERCENT:-40} / 100 / KB))
else
# Fallback: assume 4GB
echo $((4 * GB / KB))
@@ -284,33 +296,35 @@ calc_effective_io_concurrency() {
}
# Calculate parallel workers settings
# Echoes 4 space-separated values: max_worker_processes max_parallel_workers_per_gather max_parallel_workers max_parallel_maintenance_workers
# (single source of truth — generate_env_vars reads these instead of duplicating the logic)
calc_parallel_settings() {
if [[ $CPU_COUNT -lt 4 ]]; then
echo ""
return
local max_worker_processes max_parallel_workers_per_gather max_parallel_workers max_parallel_maintenance_workers
if [[ $CPU_COUNT -ge 4 ]]; then
local workers_per_gather=$((CPU_COUNT / 2))
# Cap at 4 for non-DW workloads
if [[ "$PG_DB_TYPE" != "$DB_TYPE_DW" && $workers_per_gather -gt 4 ]]; then
workers_per_gather=4
fi
local parallel_maint_workers=$((CPU_COUNT / 2))
if [[ $parallel_maint_workers -gt 4 ]]; then
parallel_maint_workers=4
fi
max_worker_processes=${PG_MAX_WORKER_PROCESSES:-$CPU_COUNT}
max_parallel_workers_per_gather=${PG_MAX_PARALLEL_WORKERS_PER_GATHER:-$workers_per_gather}
max_parallel_workers=${PG_MAX_PARALLEL_WORKERS:-$CPU_COUNT}
max_parallel_maintenance_workers=${PG_MAX_PARALLEL_MAINTENANCE_WORKERS:-$parallel_maint_workers}
else
max_worker_processes=${PG_MAX_WORKER_PROCESSES:-8}
max_parallel_workers_per_gather=${PG_MAX_PARALLEL_WORKERS_PER_GATHER:-2}
max_parallel_workers=${PG_MAX_PARALLEL_WORKERS:-8}
max_parallel_maintenance_workers=${PG_MAX_PARALLEL_MAINTENANCE_WORKERS:-2}
fi
local workers_per_gather=$((CPU_COUNT / 2))
local max_parallel_workers=${PG_MAX_PARALLEL_WORKERS:-$CPU_COUNT}
local max_worker_processes=${PG_MAX_WORKER_PROCESSES:-$CPU_COUNT}
# Cap at 4 for non-DW workloads
if [[ "$PG_DB_TYPE" != "$DB_TYPE_DW" && $workers_per_gather -gt 4 ]]; then
workers_per_gather=4
fi
local parallel_maint_workers=$((CPU_COUNT / 2))
if [[ $parallel_maint_workers -gt 4 ]]; then
parallel_maint_workers=4
fi
local max_parallel_maintenance_workers=${PG_MAX_PARALLEL_MAINTENANCE_WORKERS:-$parallel_maint_workers}
local max_parallel_workers_per_gather=${PG_MAX_PARALLEL_WORKERS_PER_GATHER:-$workers_per_gather}
echo "max_worker_processes = $max_worker_processes"
echo "max_parallel_workers_per_gather = $max_parallel_workers_per_gather"
echo "max_parallel_workers = $max_parallel_workers"
echo "max_parallel_maintenance_workers = $max_parallel_maintenance_workers"
echo "$max_worker_processes $max_parallel_workers_per_gather $max_parallel_workers $max_parallel_maintenance_workers"
}
# Calculate work_mem
@@ -322,8 +336,10 @@ calc_work_mem() {
local shared_buffers_kb=$1
local max_connections=$2
local max_worker_processes=${PG_MAX_WORKER_PROCESSES:-8}
# the actual computed worker count, passed in by generate_env_vars so the divisor stays
# consistent with the exported max_worker_processes (was previously assumed to be 8)
local max_worker_processes=${3:-${PG_MAX_WORKER_PROCESSES:-8}}
local work_mem_kb=$(( (TOTAL_MEMORY_KB - shared_buffers_kb) / ((max_connections + max_worker_processes) * 3) ))
case "$PG_DB_TYPE" in
@@ -332,9 +348,17 @@ calc_work_mem() {
;;
esac
# Minimum 64KB
if [[ $work_mem_kb -lt 64 ]]; then
work_mem_kb=64
# Floor for web/oltp/mixed app workloads: below ~16MB, many-join queries (sorts + hashes)
# constantly spill to disk-based algorithms writing temp files (measured multi-TB on a busy
# instance at 16.6MB with an oversized max_connections divisor). Other profiles keep 64KB.
local min_work_mem=64
case "$PG_DB_TYPE" in
"$DB_TYPE_WEB"|"$DB_TYPE_OLTP"|"$DB_TYPE_MIXED")
min_work_mem=$((16 * MB / KB))
;;
esac
if [[ $work_mem_kb -lt $min_work_mem ]]; then
work_mem_kb=$min_work_mem
fi
echo "$work_mem_kb"
@@ -360,6 +384,48 @@ calc_checkpoint_completion_target() {
echo "${PG_CHECKPOINT_COMPLETION_TARGET:-0.9}"
}
# Calculate autovacuum_max_workers (~1/3 of cores, min 3 which is also the PG default)
calc_autovacuum_max_workers() {
if [[ -n "${PG_AUTOVACUUM_MAX_WORKERS:-}" ]]; then
echo "$PG_AUTOVACUUM_MAX_WORKERS"
return
fi
local workers=$((CPU_COUNT / 3))
if [[ $workers -lt 3 ]]; then
workers=3
fi
echo "$workers"
}
# WAL compression: lz4 is cheaper than the pglz default but needs PG >= 15
calc_wal_compression() {
if [[ -n "${PG_WAL_COMPRESSION:-}" ]]; then
echo "$PG_WAL_COMPRESSION"
return
fi
if [[ "${PG_DB_VERSION%%.*}" -ge 15 ]]; then
echo "lz4"
else
echo "off"
fi
}
# TOAST compression for large values (post content etc.): lz4 needs PG >= 14
calc_toast_compression() {
if [[ -n "${PG_TOAST_COMPRESSION:-}" ]]; then
echo "$PG_TOAST_COMPRESSION"
return
fi
if [[ "${PG_DB_VERSION%%.*}" -ge 14 ]]; then
echo "lz4"
else
echo "pglz"
fi
}
# Main generation function - output as ENV vars
generate_env_vars() {
local max_connections shared_buffers_kb effective_cache_kb maint_work_mem_kb
@@ -376,33 +442,20 @@ generate_env_vars() {
default_stats_target=$(calc_default_statistics_target)
random_page_cost=$(calc_random_page_cost)
effective_io_concurrency=$(calc_effective_io_concurrency)
work_mem_kb=$(calc_work_mem "$shared_buffers_kb" "$max_connections")
huge_pages=$(calc_huge_pages)
checkpoint_target=$(calc_checkpoint_completion_target)
# Calculate parallel settings as individual values
# Parallel settings (single source: calc_parallel_settings), needed BEFORE work_mem
# since the worker count is part of its divisor
local max_worker_processes max_parallel_workers_per_gather max_parallel_workers max_parallel_maintenance_workers
if [[ $CPU_COUNT -ge 4 ]]; then
local workers_per_gather=$((CPU_COUNT / 2))
if [[ "$PG_DB_TYPE" != "$DB_TYPE_DW" && $workers_per_gather -gt 4 ]]; then
workers_per_gather=4
fi
local parallel_maint_workers=$((CPU_COUNT / 2))
if [[ $parallel_maint_workers -gt 4 ]]; then
parallel_maint_workers=4
fi
read -r max_worker_processes max_parallel_workers_per_gather max_parallel_workers max_parallel_maintenance_workers <<< "$(calc_parallel_settings)"
max_worker_processes=${PG_MAX_WORKER_PROCESSES:-$CPU_COUNT}
max_parallel_workers_per_gather=${PG_MAX_PARALLEL_WORKERS_PER_GATHER:-$workers_per_gather}
max_parallel_workers=${PG_MAX_PARALLEL_WORKERS:-$CPU_COUNT}
max_parallel_maintenance_workers=${PG_MAX_PARALLEL_MAINTENANCE_WORKERS:-$parallel_maint_workers}
else
max_worker_processes=${PG_MAX_WORKER_PROCESSES:-8}
max_parallel_workers_per_gather=${PG_MAX_PARALLEL_WORKERS_PER_GATHER:-2}
max_parallel_workers=${PG_MAX_PARALLEL_WORKERS:-8}
max_parallel_maintenance_workers=${PG_MAX_PARALLEL_MAINTENANCE_WORKERS:-2}
fi
work_mem_kb=$(calc_work_mem "$shared_buffers_kb" "$max_connections" "$max_worker_processes")
local autovacuum_max_workers wal_compression toast_compression
autovacuum_max_workers=$(calc_autovacuum_max_workers)
wal_compression=$(calc_wal_compression)
toast_compression=$(calc_toast_compression)
# Output ENV vars (only if not already set)
cat << EOF
@@ -424,6 +477,27 @@ export PG_MAX_WORKER_PROCESSES=\${PG_MAX_WORKER_PROCESSES:-$max_worker_processes
export PG_MAX_PARALLEL_WORKERS_PER_GATHER=\${PG_MAX_PARALLEL_WORKERS_PER_GATHER:-$max_parallel_workers_per_gather}
export PG_MAX_PARALLEL_WORKERS=\${PG_MAX_PARALLEL_WORKERS:-$max_parallel_workers}
export PG_MAX_PARALLEL_MAINTENANCE_WORKERS=\${PG_MAX_PARALLEL_MAINTENANCE_WORKERS:-$max_parallel_maintenance_workers}
# JIT: a per-execution LLVM compile tax on complex-but-fast app queries; off unless doing long analytics scans
export PG_JIT=\${PG_JIT:-off}
# observability: query stats extension (restart-required to change), I/O timing, full query texts in pg_stat_activity
export PG_PRELOAD_LIBS=\${PG_PRELOAD_LIBS:-pg_stat_statements}
export PG_TRACK_IO_TIMING=\${PG_TRACK_IO_TIMING:-on}
export PG_TRACK_ACTIVITY_QUERY_SIZE=\${PG_TRACK_ACTIVITY_QUERY_SIZE:-16384}
# autovacuum tuned for soft-delete/append app workloads (default 20% thresholds never trigger on big tables);
# per-table thresholds for the hottest tables also ship as an app migration
export PG_AUTOVACUUM_VACUUM_SCALE_FACTOR=\${PG_AUTOVACUUM_VACUUM_SCALE_FACTOR:-0.05}
export PG_AUTOVACUUM_VACUUM_INSERT_SCALE_FACTOR=\${PG_AUTOVACUUM_VACUUM_INSERT_SCALE_FACTOR:-0.05}
export PG_AUTOVACUUM_ANALYZE_SCALE_FACTOR=\${PG_AUTOVACUUM_ANALYZE_SCALE_FACTOR:-0.02}
export PG_AUTOVACUUM_VACUUM_COST_LIMIT=\${PG_AUTOVACUUM_VACUUM_COST_LIMIT:-1000}
export PG_AUTOVACUUM_MAX_WORKERS=\${PG_AUTOVACUUM_MAX_WORKERS:-$autovacuum_max_workers}
export PG_LOG_AUTOVACUUM_MIN_DURATION=\${PG_LOG_AUTOVACUUM_MIN_DURATION:-0}
# cheaper compression codecs (version-gated: wal lz4 needs PG15+, toast lz4 PG14+)
export PG_WAL_COMPRESSION=\${PG_WAL_COMPRESSION:-$wal_compression}
export PG_TOAST_COMPRESSION=\${PG_TOAST_COMPRESSION:-$toast_compression}
# diagnostics logging: slow statements (server-side; the app logs its own from 100ms), lock waits, temp-file spills
export PG_LOG_MIN_DURATION_STATEMENT=\${PG_LOG_MIN_DURATION_STATEMENT:-2000}
export PG_LOG_LOCK_WAITS=\${PG_LOG_LOCK_WAITS:-on}
export PG_LOG_TEMP_FILES=\${PG_LOG_TEMP_FILES:-0}
EOF
}
+16
View File
@@ -224,6 +224,7 @@ wal_buffers = ${PG_WAL_BUFFERS_MB}MB # min 32kB, -1 sets based on shared_buffers
#full_page_writes = on # recover from partial page writes
#wal_log_hints = off # also do full page writes of non-critical updates
# (change requires restart)
wal_compression = ${PG_WAL_COMPRESSION} # cheaper full-page writes (lz4 on PG15+; computed by postgres-tune.sh)
#wal_compression = off # enables compression of full-page writes;
# off, pglz, lz4, zstd, or on
#wal_init_zero = on # zero-fill new WAL files
@@ -430,6 +431,7 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
#constraint_exclusion = partition # on, off, or partition
#cursor_tuple_fraction = 0.1 # range 0.0-1.0
#from_collapse_limit = 8
jit = ${PG_JIT} # per-execution LLVM compile tax on complex-but-fast app queries; off by default (tune with PG_JIT)
#jit = on # allow JIT compilation
#join_collapse_limit = 8 # 1 disables collapsing of explicit
# JOIN clauses
@@ -517,6 +519,7 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
# fatal
# panic (effectively off)
log_min_duration_statement = ${PG_LOG_MIN_DURATION_STATEMENT} # server-side slow-statement log (the app logs its own from 100ms)
#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements
# and their durations, > 0 logs only
# statements running at least this number
@@ -548,6 +551,7 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
#debug_print_rewritten = off
#debug_print_plan = off
#debug_pretty_print = on
log_autovacuum_min_duration = ${PG_LOG_AUTOVACUUM_MIN_DURATION} # log every autovacuum run — the regression tripwire for autovacuum-never-runs
#log_autovacuum_min_duration = 10min # log autovacuum activity;
# -1 disables, 0 logs all actions and
# their durations, > 0 logs only
@@ -583,6 +587,7 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
# processes
# %% = '%'
# e.g. '<%u%%%d> '
log_lock_waits = ${PG_LOG_LOCK_WAITS}
#log_lock_waits = off # log lock waits >= deadlock_timeout
#log_recovery_conflict_waits = off # log standby recovery conflict waits
# >= deadlock_timeout
@@ -594,6 +599,7 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
# -1 means print in full, 0 disables
#log_statement = 'none' # none, ddl, mod, all
#log_replication_commands = off
log_temp_files = ${PG_LOG_TEMP_FILES} # log all temp-file spills (the work_mem-too-small signal)
#log_temp_files = -1 # log temporary files equal or larger
# than the specified size in kilobytes;
# -1 disables, 0 logs all temp files
@@ -613,8 +619,10 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
# - Cumulative Query and Index Statistics -
#track_activities = on
track_activity_query_size = ${PG_TRACK_ACTIVITY_QUERY_SIZE} # full query texts in pg_stat_activity (change requires restart)
#track_activity_query_size = 1024 # (change requires restart)
#track_counts = on
track_io_timing = ${PG_TRACK_IO_TIMING} # needed for pg_stat_statements I/O attribution
#track_io_timing = off
#track_wal_io_timing = off
#track_functions = none # none, pl, all
@@ -636,6 +644,7 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
#autovacuum = on # Enable autovacuum subprocess? 'on'
# requires track_counts to also be on.
autovacuum_max_workers = ${PG_AUTOVACUUM_MAX_WORKERS} # (change requires restart)
#autovacuum_max_workers = 3 # max number of autovacuum subprocesses
# (change requires restart)
#autovacuum_naptime = 1min # time between autovacuum runs
@@ -646,9 +655,12 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
# vacuums
#autovacuum_analyze_threshold = 50 # min number of row updates before
# analyze
autovacuum_vacuum_scale_factor = ${PG_AUTOVACUUM_VACUUM_SCALE_FACTOR} # default 0.2 never triggers on big tables
#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum
autovacuum_vacuum_insert_scale_factor = ${PG_AUTOVACUUM_VACUUM_INSERT_SCALE_FACTOR} # key for append-mostly workloads: keeps visibility maps fresh for index-only scans
#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table
# size before insert vacuum
autovacuum_analyze_scale_factor = ${PG_AUTOVACUUM_ANALYZE_SCALE_FACTOR}
#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze
#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
# (change requires restart)
@@ -658,6 +670,7 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for
# autovacuum, in milliseconds;
# -1 means use vacuum_cost_delay
autovacuum_vacuum_cost_limit = ${PG_AUTOVACUUM_VACUUM_COST_LIMIT} # default (200 via vacuum_cost_limit) is glacial on multi-GB tables
#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for
# autovacuum, -1 means use
# vacuum_cost_limit
@@ -683,6 +696,7 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
#row_security = on
#default_table_access_method = 'heap'
#default_tablespace = '' # a tablespace name, '' uses the default
default_toast_compression = ${PG_TOAST_COMPRESSION} # lz4 on PG14+ (computed by postgres-tune.sh)
#default_toast_compression = 'pglz' # 'pglz' or 'lz4'
#temp_tablespaces = '' # a list of tablespace names, '' uses
# only default tablespace
@@ -741,6 +755,7 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
#local_preload_libraries = ''
#session_preload_libraries = ''
shared_preload_libraries = '${PG_PRELOAD_LIBS}' # pg_stat_statements etc. (change requires restart)
#shared_preload_libraries = '' # (change requires restart)
#jit_provider = 'llvmjit' # JIT library to use
@@ -817,3 +832,4 @@ default_statistics_target = ${PG_DEFAULT_STATISTICS_TARGET} # range 1-10000
#------------------------------------------------------------------------------
# Add settings for extensions here
pg_stat_statements.track = all
+1
View File
@@ -0,0 +1 @@
ATTENTION: this is the first release and contains a couple of breaking changes in comparison to the previously unreleased recipe. There are a lot of secrets, keys and passwords moved to docker secrets, and the .env.sample was completly restructured. It is recommended to start your env file from scratch from the new template to make sure nothing is missing.