23 Commits
Author SHA1 Message Date
mayel 863538311d avoid postgres config clobber 2026-10-22 15:42:13 +01:00
mayel 5dc3d86de0 add optional nginx server for uploads + maintanance page 2026-10-21 09:33:54 +01:00
mayel 85201a8fa7 add csp for uploads 2026-10-04 14:16:24 +01:00
mayel fd6280026a add env 2026-10-02 21:20:42 +01:00
mayel 09383b8f89 update sonic config 2026-09-10 10:39:55 +01:00
stevensting 90f5ff3fbb chore: publish 2.0.2+1.0.7-social-amd64 release 2026-09-02 09:37:19 +02:00
stevensting 29bf8a4dfa increase bonfire version to 1.0.7 2026-09-02 09:34:45 +02:00
stevensting d9b24e61a7 add conf file versions to get rid of warnings during deploy 2026-09-02 09:33:21 +02:00
renovate-bot c30b8cd57a chore(deps): update valeriansaliou/sonic docker tag to v1.8.1 (#20)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [valeriansaliou/sonic](https://github.com/valeriansaliou/sonic) | minor | `v1.7.4` -> `v1.8.1` |

> ❗ **Important**
>
> Release Notes retrieval for this PR were skipped because no github.com credentials were available.
> If you are self-hosted, please see [this instruction](https://github.com/renovatebot/renovate/blob/master/docs/usage/examples/self-hosting.md#githubcom-token-for-release-notes).

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNzMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE3My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #20
Co-authored-by: Renovate Bot <renovate@coopcloud.tech>
2026-09-02 07:07:34 +00:00
mayel f771ba08f3 Merge pull request 'add BONFIRE_CONSOLE_TOKEN' (#21) from console-token into main
Reviewed-on: #21
2026-08-26 13:47:31 +00:00
stevensting 398f3c2bbb add env var 2026-08-18 16:34:16 +02:00
mayel 48e7e26bb0 Merge pull request 'update documentation' (#19) from more-doc into main
Reviewed-on: #19
2026-08-05 09:04:08 +00:00
stevensting 919455212a refine admin account creation 2026-08-05 08:50:24 +00:00
stevensting a49cdecfd8 chore: publish 2.0.1+1.0.6-social-amd64 release 2026-08-04 16:03:16 +02:00
stevensting 576f2e2dda increase bonfire version to 1.0.6 2026-08-04 16:00:27 +02:00
stevensting ead44b7103 chore: publish 2.0.0+1.0.5-social-amd64 release 2026-08-04 15:56:10 +02:00
stevensting c00d5b4a16 add documentation 2026-08-04 15:51:30 +02:00
mayel 4aacfaa0ee Merge pull request 'remove meilisearch' (#18) from remove-meilisearch into main
Reviewed-on: #18
2026-08-04 12:46:07 +00:00
stevensting 02ec8ba161 remove meilisearch 2026-08-04 14:28:34 +02:00
renovate-bot fb0b664373 chore(deps): update valeriansaliou/sonic docker tag to v1.7.4 (#11)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [valeriansaliou/sonic](https://github.com/valeriansaliou/sonic) | minor | `v1.5.1` -> `v1.7.4` |

> ❗ **Important**
>
> Release Notes retrieval for this PR were skipped because no github.com credentials were available.
> If you are self-hosted, please see [this instruction](https://github.com/renovatebot/renovate/blob/master/docs/usage/examples/self-hosting.md#githubcom-token-for-release-notes).

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNzMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE3My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: #11
Co-authored-by: Renovate Bot <renovate@coopcloud.tech>
Co-committed-by: Renovate Bot <renovate@coopcloud.tech>
2026-08-04 11:56:59 +00:00
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
32 changed files with 708 additions and 482 deletions
+140 -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,73 @@ 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 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
# 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
#COMPOSE_FILE="$COMPOSE_FILE:compose.sonic.yml"
# docker secret cannot be used due to distroless image, threfore add secret here
#SONIC_PASSWORD=
# ====================================
# STATIC FILES & MAINTENANCE PAGE
## serve uploads with nginx (bypassing Phoenix) and show a maintenance page while the app restarts. NOTE: don't combine with BASIC_AUTH, as uploads would bypass it
#COMPOSE_FILE="$COMPOSE_FILE:compose.static.yml"
# ====================================
# 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 +97,126 @@ 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_ISSUER=
# 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
+33 -13
View File
@@ -15,24 +15,42 @@ A [coop-cloud](https://coopcloud.tech) recipe for deploying [Bonfire](https://bo
## Basic usage
1. Set up Docker Swarm and [`abra`]
2. Deploy [`coop-cloud/traefik`]
3. `abra app new bonfire --secrets` (optionally with `--pass` if you'd like to save secrets in `pass`) and select your server from the list and enter the domain name you want Bonfire to be served from
4. `abra app config YOUR_APP_DOMAIN_NAME` and check/edit the config keys
5. `abra app deploy YOUR_APP_DOMAIN_NAME`
6. Open the configured domain in your browser and sign up!
1. `abra app new bonfire --secrets`
2. `abra app config <app-name>`
3. `abra app deploy <app-name>`
4. Open the Elixir console: `abra app run <app-name> app bin/bonfire remote`
5. Create admin account in Elixir console (the first account created this way will get admin rights): `Bonfire.Me.make_account_only("my@email.net", "my pw")`
## Upgrades
`abra app deploy --force your-server.domain.name`
## Use a custom Bonfire flavour
NOTE: we recommend switching to the new `sonic` search backend (instead of the deprecated `meilisearch`):
1. comment the `COMPOSE_FILE` line that contains `compose.meilisearch.yml` in the `.env` file for your bonfire instancem and replace with a line like `COMPOSE_FILE="compose.yml:compose.sonic.yml"`
By default, this recipe deploys the Bonfire `social` flavour. If you want a different one uncomment and define `APP_FLAVOUR` in you env file.
## Use a custom Bonfire version
Attention: This is not recommended for production deployment!
You can deploy another version of Bonfire by using `APP_VERSION` and `APP_PLATFORM`. Be aware that not all versions might be compatible with this recipe, especially the quite old ones.
If you want to use bleeding edge releases try setting `APP_VERSION=latest-alpha`. When using this, every time you deploy with `abra app deploy --force <app-name>` the latest alpha replease will be used.
## Using a search backend
1. uncomment the `COMPOSE_FILE` line that contains `compose.sonic.yml` in the `.env` file for your bonfire instance.
2. add `SONIC_PASSWORD=a-super-secret-password` to the same file (make sure to change the password after pasting!)
3. redeploy with `abra app deploy --force your-server.domain.name`
3. redeploy with `abra app deploy --force <app-name>`
## Serve uploads with nginx and show a maintenance page
[`abra`]: https://docs.coopcloud.tech/abra/
[`coop-cloud/traefik`]: https://git.coopcloud.tech/coop-cloud/traefik
An optional nginx service serves uploaded files straight from the uploads volume, so they don't go through the app, and keep working while it restarts. It also shows a maintenance page whenever the app is down, such as during upgrades. Uncomment the `COMPOSE_FILE` line that contains `compose.static.yml` in the `.env` file for your bonfire instance to enable.
## Protect the instance with basic auth
For e.g. a testing phase you can protect your Bonfire instance with basic auth:
1. uncomment the section `## BASIC_AUTH`
2. create a username/password combination with `echo $(htpasswd -nB <username>)`
3. add the combination as secret with `abra app secret insert <app-name> usersfile v1 '<the-user-password-combination>'`
##
## FAQ
@@ -44,3 +62,5 @@ Go into your app's Elixir console and enter something like `Bonfire.Me.make_acco
### How can I get to the app's Elixir console?
`abra app run your-server.domain.name app bin/bonfire remote`
For more, see [docs.coopcloud.tech](https://docs.coopcloud.tech).
+8 -2
View File
@@ -1,5 +1,11 @@
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
export SONIC_CFG_VERSION=v2
export SONIC_ENTRYPOINT_VERSION=v1
export POSTGRES_ENTRYPOINT_VERSION=v7
export POSTGRES_TUNE_VERSION=v2
export POSTGRES_CONF_TMPL_VERSION=v3
export BASH_ENVSUBST_VERSION=v1
export STATIC_NGINX_CONF_VERSION=v1
export MAINTENANCE_HTML_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}
+20
View File
@@ -0,0 +1,20 @@
version: "3.8"
services:
app:
environment:
- OPENID_1_DISPLAY_NAME
- OPENID_1_ISSUER
- 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}
+2
View File
@@ -7,6 +7,8 @@ services:
labels:
- "traefik.http.middlewares.${STACK_NAME}_basicauth.basicauth.usersFile=/run/secrets/usersfile"
- "traefik.http.routers.${STACK_NAME}.middlewares=${STACK_NAME}_basicauth"
# the uploads router (see compose.yml) needs the basic auth too, or uploads would bypass it
- "traefik.http.routers.${STACK_NAME}_uploads.middlewares=${STACK_NAME}_basicauth,${STACK_NAME}_uploads_csp"
secrets:
usersfile:
+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}
-44
View File
@@ -1,44 +0,0 @@
---
version: "3.8"
services:
app:
depends_on:
- search
environment:
- SEARCH_MEILI_INSTANCE=http://${STACK_NAME}_search:7700
search:
image: getmeili/meilisearch:v1.14 # WIP: upgrade from v1.11 to 1.14
secrets:
- meili_master_key
volumes:
- "search-data:/meili_data"
- "dump-data:/meili_dumps"
networks:
- internal
entrypoint: ["tini", "--", "/docker-entrypoint.sh", "/bin/meilisearch"]
environment:
- MEILI_DUMP_DIR=/meili_dumps
configs:
- source: app_entrypoint
target: /docker-entrypoint.sh
mode: 0555
- source: meili_backup
target: /meili_backup.sh
mode: 0555
labels:
backupbot.backup: ${ENABLE_BACKUPS:-true}
backupbot.backup.volumes.search-data: "false"
backupbot.backup.volumes.dump-data.path: "/meili_dumps/meilisearch_latest.dump"
backupbot.backup.pre-hook: "/meili_backup.sh backup"
backupbot.restore.post-hook: '/meili_backup.sh restore'
volumes:
search-data:
dump-data:
configs:
meili_backup:
name: ${STACK_NAME}_meili_backup_${MEILI_BACKUP_VERSION:-v4}
file: meili_backup.sh
+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}
+1 -1
View File
@@ -15,7 +15,7 @@ services:
# - sonic_password
search:
image: valeriansaliou/sonic:v1.5.1
image: valeriansaliou/sonic:v1.8.1
# secrets:
# - sonic_password
volumes:
+53
View File
@@ -0,0 +1,53 @@
---
version: "3.8"
# Opt-in nginx sidecar that serves uploads directly from the volume (bypassing Phoenix) and shows a maintenance page whenever the app has no running task (e.g. during redeploys and upgrades). NOTE: don't combine with `compose.basicauth.yml`, as uploads served from here would bypass the basic auth.
services:
static:
image: nginx:1.29.0-alpine
networks:
- proxy
volumes:
# NOTE: the app and this service share the local volume, so they must run on the same node
- upload-data:/srv/data/uploads:ro
configs:
- source: static_nginx_conf
target: /etc/nginx/conf.d/default.conf
mode: 0444
- source: maintenance_html
target: /srv/maintenance/maintenance.html
mode: 0444
healthcheck:
# 127.0.0.1 rather than localhost, as nginx only listens on IPv4
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1/maintenance.html"]
interval: 30s
timeout: 10s
retries: 10
start_period: 5s
deploy:
restart_policy:
condition: on-failure
labels:
- "traefik.enable=true"
- "traefik.http.services.${STACK_NAME}_static.loadbalancer.server.port=80"
# serve uploads from here rather than through the app's `_uploads` router (which has the same rule, so this one needs an explicit higher priority)
- "traefik.http.routers.${STACK_NAME}_static_uploads.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS}) && PathPrefix(`/data/uploads/`)"
- "traefik.http.routers.${STACK_NAME}_static_uploads.priority=10000"
- "traefik.http.routers.${STACK_NAME}_static_uploads.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}_static_uploads.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.routers.${STACK_NAME}_static_uploads.service=${STACK_NAME}_static"
# lowest priority fallback: Traefik drops the app's routers while it has no running task, so requests land here and get the maintenance page
- "traefik.http.routers.${STACK_NAME}_maintenance.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS})"
- "traefik.http.routers.${STACK_NAME}_maintenance.priority=1"
- "traefik.http.routers.${STACK_NAME}_maintenance.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}_maintenance.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.routers.${STACK_NAME}_maintenance.service=${STACK_NAME}_static"
configs:
static_nginx_conf:
name: ${STACK_NAME}_static_nginx_conf_${STATIC_NGINX_CONF_VERSION:-v1}
file: static.nginx.conf
maintenance_html:
name: ${STACK_NAME}_maintenance_html_${MAINTENANCE_HTML_VERSION:-v1}
file: maintenance.html
+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}
+43 -175
View File
@@ -3,7 +3,7 @@ version: "3.8"
services:
app:
image: ${APP_DOCKER_IMAGE}
image: ${CONTAINER_REGISTRY:-bonfirenetworks/bonfire}:${APP_VERSION:-1.0.7}-${APP_FLAVOUR:-social}-${APP_PLATFORM:-amd64}
logging:
driver: "json-file"
options:
@@ -12,28 +12,28 @@ 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
@@ -42,96 +42,37 @@ services:
- DB_MIGRATE_INDEXES_CONCURRENTLY
- PROD_LOG_LEVEL
- 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
- 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
- BONFIRE_CONSOLE_TOKEN
- ORCID_CLIENT_ID
- ORCID_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
- 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
entrypoint: ["/docker-entrypoint.sh", "./bin/bonfire", "start"]
# give the app time to shut down cleanly: on SIGTERM Oban stops fetching jobs and waits up to its `shutdown_grace_period` (default 15s) for running ones to finish, then the endpoint drains LiveView sockets and waits up to Thousand Island's `shutdown_timeout` (default 15s) for HTTP requests. These run one after the other, but the Swarm default (10s) force-kills the app first on every redeploy. Keep this above their sum.
stop_grace_period: 42s
configs:
- source: app_entrypoint
target: /docker-entrypoint.sh
@@ -144,95 +85,28 @@ services:
# 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}
# 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
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
# 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
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
# uploads (e.g. SVG) are served from the instance's origin, so stop any script in them from running if opened directly
- "traefik.http.middlewares.${STACK_NAME}_uploads_csp.headers.customresponseheaders.Content-Security-Policy=sandbox; default-src 'none'; style-src 'unsafe-inline'"
- "traefik.http.routers.${STACK_NAME}_uploads.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS}) && PathPrefix(`/data/uploads/`)"
- "traefik.http.routers.${STACK_NAME}_uploads.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}_uploads.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "traefik.http.routers.${STACK_NAME}_uploads.service=${STACK_NAME}"
- "traefik.http.routers.${STACK_NAME}_uploads.middlewares=${STACK_NAME}_uploads_csp"
- "coop-cloud.${STACK_NAME}.version=2.0.3+1.0.7-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:
@@ -244,14 +118,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}
@@ -261,12 +129,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 $@...."
+22
View File
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="refresh" content="30">
<title>Back in a moment</title>
<style>
body { margin: 0; min-height: 100vh; display: grid; place-items: center; font-family: system-ui, sans-serif; background: #1d1f21; color: #e5e5e5; text-align: center; }
main { max-width: 32rem; padding: 2rem; }
h1 { font-size: 1.5rem; }
p { line-height: 1.5; opacity: 0.8; }
</style>
</head>
<body>
<main>
<h1>Back in a moment</h1>
<p>This site is offline, possibly restarting for an upgrade.</p>
<p>Will try again in a few seconds...</p>
</main>
</body>
</html>
-107
View File
@@ -1,107 +0,0 @@
#!/bin/sh
set -e
backup() {
SECRET=$(cat /run/secrets/meili_master_key)
# Create dump
echo "Creating new Meilisearch dump..."
RESPONSE=$(curl -s -X POST 'http://localhost:7700/dumps' -H "Authorization: Bearer $SECRET")
echo "Response: $RESPONSE"
# More robust extraction of task UID
TASK_UID=$(echo "$RESPONSE" | sed -n 's/.*"taskUid":\([0-9]*\).*/\1/p')
if [ -z "$TASK_UID" ]; then
echo "Failed to extract task UID from response. Aborting."
exit 1
fi
echo "Waiting for dump creation (task $TASK_UID)..."
MAX_ATTEMPTS=600
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
RESPONSE=$(curl -s "http://localhost:7700/tasks/$TASK_UID" -H "Authorization: Bearer $SECRET")
echo "Task status response: $RESPONSE"
TASK_STATUS=$(echo "$RESPONSE" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')
if [ -z "$TASK_STATUS" ]; then
echo "Failed to extract task status. Retrying..."
ATTEMPT=$((ATTEMPT+1))
sleep 5
continue
fi
echo "Current status: $TASK_STATUS"
if [ "$TASK_STATUS" = "succeeded" ]; then
echo "Dump creation succeeded"
break
elif [ "$TASK_STATUS" = "enqueued" ] || [ "$TASK_STATUS" = "processing" ]; then
echo "Dump creation in progress... ($TASK_STATUS)"
ATTEMPT=$((ATTEMPT+1))
sleep 5
else
echo "Dump creation in unexpected state: $TASK_STATUS. Giving up."
exit 1
fi
done
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "Timed out waiting for dump creation"
exit 1
fi
# Extract dump UID more reliably
DUMP_UID=$(echo "$RESPONSE" | sed -n 's/.*"dumpUid":"\([^"]*\)".*/\1/p')
if [ -z "$DUMP_UID" ]; then
echo "Failed to extract dump UID. Aborting."
exit 1
fi
echo "Using dump $DUMP_UID"
# Check if file exists before copying
if [ ! -f "/meili_dumps/$DUMP_UID.dump" ]; then
echo "Dump file /meili_dumps/$DUMP_UID.dump not found!"
ls -la /meili_dumps/
exit 1
fi
cp -f "/meili_dumps/$DUMP_UID.dump" "/meili_dumps/meilisearch_latest.dump"
echo "Dump created and copied successfully. You can find it at /meili_dumps/meilisearch_latest.dump"
}
restore() {
echo 'Restarting Meilisearch with imported dump, may take a while to become available...'
# Check if dump file exists
if [ ! -f "/meili_dumps/meilisearch_latest.dump" ]; then
echo "Error: Dump file not found at /meili_dumps/meilisearch_latest.dump"
exit 1
fi
pkill meilisearch || echo "No Meilisearch process found to kill"
echo "Starting Meilisearch with import dump option..."
MEILI_NO_ANALYTICS=true /bin/meilisearch --import-dump /meili_dumps/meilisearch_latest.dump &
echo "Meilisearch restore process initiated..."
}
# Handle command line argument
case "$1" in
backup)
backup
;;
restore)
restore
;;
*)
echo "Usage: $0 {backup|restore}"
exit 1
;;
esac
+1 -1
View File
@@ -39,7 +39,7 @@ services:
configs:
postgres_entrypoint:
name: ${STACK_NAME}_postgres_entrypoint_${POSTGRES_ENTRYPOINT_VERSION:-v6}
name: ${STACK_NAME}_postgres_entrypoint_${POSTGRES_ENTRYPOINT_VERSION:-v7}
file: postgres/postgres-entrypoint.sh
postgres_tune:
name: ${STACK_NAME}_postgres_tune_${POSTGRES_TUNE_VERSION:-v2}
+30 -4
View File
@@ -8,8 +8,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[[ "$SCRIPT_DIR" == "/" ]] && SCRIPT_DIR=""
# Configuration via environment variables
POSTGRES_CONF_PATH="${POSTGRES_CONF_PATH:-/var/lib/postgresql/data/postgresql.auto.conf}"
POSTGRES_START_CMD="${POSTGRES_START_CMD:-docker-entrypoint.sh postgres}"
# The tuned config is a standalone config_file OUTSIDE the data dir, so it never shares postgresql.auto.conf with `ALTER SYSTEM`. Bonfire's InstanceTuning clears its own ALTER SYSTEM writes on every app boot (`ALTER SYSTEM RESET`), which wiped the tuner's values when both lived in auto.conf. Now RESET falls back to this file, and admin ALTER SYSTEM values survive restarts. Outside the data dir also keeps a fresh volume empty for initdb.
# POSTGRES_CONF_PATH="${POSTGRES_CONF_PATH:-/var/lib/postgresql/data/postgresql.auto.conf}"
POSTGRES_CONF_PATH="${POSTGRES_CONF_PATH:-/etc/postgresql/bonfire.conf}"
# NOT `-c name=value` per setting: command-line values outrank postgresql.auto.conf and would silently disable InstanceTuning
POSTGRES_START_CMD="${POSTGRES_START_CMD:-docker-entrypoint.sh postgres -c config_file=${POSTGRES_CONF_PATH}}"
PGDATA="${PGDATA:-/var/lib/postgresql/data}"
# Check if envsubst is available, if not, load our bash implementation
if ! command -v envsubst &> /dev/null; then
@@ -45,10 +49,32 @@ if mkdir -p "${CONF_DIR}" 2>/dev/null; then
# Write the processed config
echo "$config" > "${POSTGRES_CONF_PATH}"
echo "PostgreSQL: Configuration generated at ${POSTGRES_CONF_PATH}"
# One-time cleanup for volumes created when this script wrote the tuned config INTO postgresql.auto.conf: those stale lines override the config_file above (and shared_buffers, max_connections etc. are not InstanceTuning knobs, so nothing else would ever clear them). Runs once per volume (marker file), so later admin ALTER SYSTEM values persist.
AUTO_CONF="${PGDATA}/postgresql.auto.conf"
TUNE_MARKER="${PGDATA}/.bonfire_tune_v2"
if [[ -f "${PGDATA}/PG_VERSION" && -f "${AUTO_CONF}" && ! -f "${TUNE_MARKER}" ]]; then
echo "PostgreSQL: removing keys the tuner now sets from ${AUTO_CONF} (one-time migration)"
tmp_conf="$(mktemp)"
# first input = the generated config (collect its keys), second = auto.conf (drop those keys, keep the rest)
awk 'NR==FNR { k=$0; sub(/[[:space:]]*=.*/, "", k); keys[k]=1; next }
{ k=$0; sub(/[[:space:]]*=.*/, "", k); if (!(k in keys)) print }' \
<(echo "$config") "${AUTO_CONF}" > "${tmp_conf}"
# write in place (not mv) so the file keeps its postgres ownership
cat "${tmp_conf}" > "${AUTO_CONF}"
rm -f "${tmp_conf}"
touch "${TUNE_MARKER}"
elif [[ ! -f "${PGDATA}/PG_VERSION" && ! -f "${TUNE_MARKER}" ]]; then
# Fresh volume: nothing to clean, but the marker can't be written yet (initdb needs an empty data dir). Write it once initdb is done, or the next boot would wrongly run the cleanup and drop admin ALTER SYSTEM values. The loop survives the exec below as an orphan and gives up after 10 minutes.
( for _ in $(seq 1 600); do
if [[ -f "${PGDATA}/PG_VERSION" ]]; then touch "${TUNE_MARKER}"; break; fi
sleep 1
done ) >/dev/null 2>&1 &
fi
# Check if we should start postgres
if [[ -n "${POSTGRES_START_CMD}" && "${POSTGRES_START_CMD}" != "none" ]]; then
# Start PostgreSQL - postgresql.auto.conf is automatically loaded from data dir
# Start PostgreSQL with the tuned config_file. postgresql.auto.conf (ALTER SYSTEM) is still loaded from the data dir and layered on top
exec ${POSTGRES_START_CMD}
else
echo "PostgreSQL: Skipping startup (POSTGRES_START_CMD is '${POSTGRES_START_CMD}')"
+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.
+1
View File
@@ -0,0 +1 @@
Mailisearch search backend was removed. If you want to use a search backend switch to sonic. See readme on how to use it.
+7 -5
View File
@@ -6,13 +6,15 @@
[channel]
inet = "0.0.0.0:1491"
# keep in sync with :keepalive_interval in Bonfire.Search.Sonic, which must stay under this or Sonic drops our idle ingest/search connections
tcp_timeout = 300
[channel.search]
query_limit_default = 10
query_limit_maximum = 200
suggest_limit_default = 5
suggest_limit_maximum = 20
# moved out of [channel.search], deprecated since Sonic 1.8
[search]
query_limit_default = 10
query_limit_maximum = 200
suggest_limit_default = 5
suggest_limit_maximum = 20
[store.kv]
path = "/var/lib/sonic/store/kv/"
+27
View File
@@ -0,0 +1,27 @@
# used by compose.static.yml
server {
listen 80;
server_tokens off;
root /srv;
# mirrors the uploads `Plug.Static` in Bonfire's endpoint
location /data/uploads/ {
# uploads (e.g. SVG) are served from the instance's origin, so stop any script in them from running if opened directly
add_header Content-Security-Policy "sandbox; default-src 'none'; style-src 'unsafe-inline'" always;
gzip_static on;
try_files $uri =404;
}
# anything else only reaches this service while the app is down (see the maintenance router in compose.static.yml)
location / {
return 503;
}
error_page 503 /maintenance.html;
location = /maintenance.html {
root /srv/maintenance;
add_header Retry-After 30 always;
add_header Cache-Control "no-store" always;
}
}