Skip to content

Deploy With Docker Compose

Deploy Buzz with the Docker Compose file on this page. It runs the published Buzz server image and Traefik on the same host. Buzz obtains the wildcard certificate itself through the Cloudflare API and publishes it to Traefik.

A deployment directory holds three files:

File Owner Purpose
docker-compose.yml Buzz release Replaced on every update. Never edit it.
.env Operator Every Buzz setting. The server reads the whole file.
compose.override.yml Operator Optional local changes such as ports, labels, or Traefik settings.

Complete these prerequisites:

  • Install Docker Engine with the Docker Compose plugin, version 2.24.4 or later.
  • Host the DNS zone of buzz.example.com on Cloudflare and point buzz.example.com and *.buzz.example.com at the host.
  • Allow inbound TCP traffic on ports 80 and 443.
  • Create a Cloudflare API token and a GitHub OAuth app.
  • Complete the access decision in the Self-Hosting Overview. Configure upstream controls before deployment if Buzz is for a closed group.

Follow Configure DNS And TLS and Configure GitHub Authentication for the required credentials.

  1. Create a directory for the deployment and download the Compose file for Buzz 0.7.1:

    Terminal window
    mkdir buzz && cd buzz
    curl -fsSLo docker-compose.yml https://raw.githubusercontent.com/infomiho/buzzstatic/server-v0.7.1/server/docker-compose.yml

    The file pins the server release and the Traefik version it was tested with:

    docker-compose.yml
    # Buzz standalone stack. Replace this file on every update and never edit it:
    # server settings live in .env, local changes in compose.override.yml.
    # Traefik reads its static configuration from the TRAEFIK_* variables below, so an
    # override can add or change single keys. Never give Traefik a command: any
    # command-line argument makes it ignore these variables.
    # Buzz obtains the certificate for BUZZ_DOMAIN and *.BUZZ_DOMAIN itself and
    # publishes it, with custom-domain routers, through the shared dynamic volume.
    services:
    traefik:
    image: traefik:v3.7.14
    restart: unless-stopped
    environment:
    TRAEFIK_PROVIDERS_DOCKER: "true"
    TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT: "false"
    TRAEFIK_PROVIDERS_FILE_DIRECTORY: /traefik/dynamic
    TRAEFIK_PROVIDERS_FILE_WATCH: "true"
    TRAEFIK_ENTRYPOINTS_WEB_ADDRESS: ":80"
    TRAEFIK_ENTRYPOINTS_WEB_HTTP_REDIRECTIONS_ENTRYPOINT_TO: websecure
    TRAEFIK_ENTRYPOINTS_WEB_HTTP_REDIRECTIONS_ENTRYPOINT_SCHEME: https
    TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS: ":443"
    TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_STORAGE: /letsencrypt/acme-http.json
    TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE_ENTRYPOINT: web
    ports:
    - "80:80"
    - "443:443"
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro
    - traefik-certs:/letsencrypt
    - traefik-dynamic:/traefik/dynamic
    server:
    image: ghcr.io/infomiho/buzzstatic:0.7.1 # x-release-please-version
    restart: unless-stopped
    env_file: .env
    environment:
    BUZZ_DATA_DIR: /data
    BUZZ_TRAEFIK_DYNAMIC_DIR: /traefik-dynamic
    BUZZ_TRAEFIK_HTTPS_ENTRYPOINT: websecure
    BUZZ_CUSTOM_DOMAIN_ORIGIN_HOST: traefik
    BUZZ_TRAEFIK_CERT_RESOLVER: letsencrypt
    BUZZ_CLOUDFLARE_DNS_API_TOKEN: ${BUZZ_CLOUDFLARE_DNS_API_TOKEN:?Set BUZZ_CLOUDFLARE_DNS_API_TOKEN in .env}
    volumes:
    - buzz-data:/data
    - traefik-dynamic:/traefik-dynamic
    labels:
    - traefik.enable=true
    - traefik.http.routers.buzz.rule=Host(`${BUZZ_DOMAIN:?Set BUZZ_DOMAIN in .env to the bare dashboard hostname, such as buzz.example.com}`)
    - traefik.http.routers.buzz.entrypoints=websecure
    - traefik.http.routers.buzz.tls=true
    - traefik.http.routers.buzz-wildcard.rule=HostRegexp(`.+\.${BUZZ_DOMAIN}`)
    - traefik.http.routers.buzz-wildcard.entrypoints=websecure
    - traefik.http.routers.buzz-wildcard.tls=true
    - traefik.http.services.buzz.loadbalancer.server.port=8080
    volumes:
    buzz-data:
    name: buzz_buzz-data
    traefik-certs:
    name: buzz_traefik-certs
    traefik-dynamic:
    name: buzz_traefik-dynamic
  2. Create a .env file next to it:

    BUZZ_DOMAIN=buzz.example.com
    GITHUB_CLIENT_ID=your-github-client-id
    GITHUB_CLIENT_SECRET=your-github-client-secret
    BUZZ_CLOUDFLARE_DNS_API_TOKEN=your-cloudflare-api-token

    Restrict access with chmod 600 .env and keep it out of source control. Add any other setting from the configuration reference to the same file. Every key in .env reaches the server, and an empty value counts as unset. Compose refuses to start when BUZZ_DOMAIN or BUZZ_CLOUDFLARE_DNS_API_TOKEN is missing, and the server exits with an ERROR: line naming any invalid value.

Pull the pinned images and start the services:

Terminal window
docker compose up -d

The Compose project creates three named volumes:

  • buzz_buzz-data stores all Buzz data at /data, including the wildcard certificate in /data/tls.
  • buzz_traefik-certs stores Traefik’s ACME state for custom domains in acme-http.json.
  • buzz_traefik-dynamic holds the certificate and routers that Buzz publishes to Traefik. Buzz rewrites it from /data.

Do not remove buzz_buzz-data or buzz_traefik-certs during routine updates.

  1. Confirm that both containers are running and the server is healthy:

    Terminal window
    docker compose ps
  2. Check the server through Traefik:

    Terminal window
    curl --fail --show-error https://buzz.example.com/health

    The first certificate takes up to a few minutes after the first start. Until then Traefik serves its default certificate and /health reports "status":"degraded". The server logs Wildcard certificate issued, valid until once it has published the certificate, and checks it through Traefik a few seconds later. If wildcard_certificate_error stays set, the server logs name the cause. Buzz retries after 2, 5, 15, and 30 minutes, and docker compose restart server retries at once after you fix the cause.

  3. Open https://buzz.example.com and start a GitHub sign-in.

If a container exits or TLS isn’t issued, inspect the logs:

Terminal window
docker compose logs server traefik

See Troubleshoot Self-Hosting for common causes.

Put local changes in compose.override.yml. Compose loads it automatically and merges it into docker-compose.yml, so it survives every update. For example, to publish different host ports and raise Traefik’s log level:

services:
traefik:
environment:
TRAEFIK_LOG_LEVEL: DEBUG
ports: !override
- "8080:80"
- "8443:443"

Traefik reads its static configuration from the TRAEFIK_* variables in docker-compose.yml. Add or change single keys under environment, as above. Do not set a command for Traefik: any command-line argument makes Traefik ignore all of these variables, so it opens no entrypoints and every site goes down.

Lists such as ports and labels are appended to by default. Use !override to replace a list.

Custom domains are disabled by default. To let site owners add their own hostnames, add these values to .env:

BUZZ_CUSTOM_DOMAINS_ENABLED=true
BUZZ_CUSTOM_DOMAIN_INGRESS_IPS=203.0.113.10

Set BUZZ_CUSTOM_DOMAIN_INGRESS_IPS to the host’s public IPv4 and IPv6 addresses, separated by commas. Recreate the services with docker compose up -d.

Buzz writes a router for each verified hostname into the shared buzz_traefik-dynamic volume. Traefik requests its certificate from Let’s Encrypt with the HTTP-01 challenge on port 80, through the letsencrypt resolver. The Compose file pins that resolver for the server, so a BUZZ_TRAEFIK_CERT_RESOLVER line in .env has no effect. Deploy With Coolify describes the site owner flow, Cloudflare proxy domains, and the operator endpoints, which work the same way here.

To disable custom domains, set BUZZ_CUSTOM_DOMAINS_ENABLED=false and run docker compose up -d. Buzz withdraws the remaining routers and logs All custom-domain routers are withdrawn.

Create a cold backup before an update and read the release notes for the versions you skip past. Then replace the Compose file with the one from the new release and recreate the services. For Buzz 0.7.1:

Terminal window
curl -fsSLo docker-compose.yml https://raw.githubusercontent.com/infomiho/buzzstatic/server-v0.7.1/server/docker-compose.yml
docker compose pull
docker compose up -d

For another release, replace the version in the URL. Your .env and compose.override.yml stay untouched.

Confirm the new release is serving:

Terminal window
curl --fail --show-error https://buzz.example.com/version

Database migrations run automatically when the new server starts, and the instance is unavailable while they run. Before migrating, Buzz saves the database to /data/backups. Migrations are forward-only: an older image refuses to start against the migrated database and names the backup to restore. To roll back, stop the stack, download the previous release’s Compose file, restore the pre-migration backup or the cold backup, and start the stack again.

The published image carries several tags:

Tag Meaning Use
<major>.<minor>.<patch> Exact release, immutable Recommended. The Compose file pins this form.
<major>.<minor> Latest patch release in the series Unattended patch updates.
latest Latest release Not recommended. Deployments stop being reproducible.

To run another tag, set image: for the server service in compose.override.yml. The pinned Compose file then no longer decides the server version, so remove the override before the next update.

For update notifications, watch releases on the GitHub repository or point Diun at the running containers. Avoid tools that auto-pull and restart running containers: an unattended update can run a database migration you did not plan for.

This release moves wildcard issuance from Traefik into Buzz, configures Traefik through environment variables, and reads .env through env_file.

  1. Create a cold backup of both volumes, and copy .env, docker-compose.yml, and compose.override.yml if it exists.
  2. Move those edits into compose.override.yml. Traefik settings become TRAEFIK_* keys under services.traefik.environment, as in Customize The Deployment. Never give Traefik a command.
  3. Edit .env:
    • Rename CF_API_TOKEN to BUZZ_CLOUDFLARE_DNS_API_TOKEN.
    • Delete ACME_EMAIL.
    • Delete BUZZ_TRAEFIK_CONTROL_TOKEN, BUZZ_CUSTOM_DOMAIN_ACME_CA_SERVER, BUZZ_TRAEFIK_CERT_RESOLVER, BUZZ_CUSTOM_DOMAIN_ROUTING_ENABLED, BUZZ_CUSTOM_DOMAIN_ADMISSION_ENABLED, BUZZ_CLOUDFLARE_DIAGNOSTICS_ENABLED, BUZZ_CLOUDFLARE_ACTIVATION_ENABLED, and BUZZ_AUTOMATIC_DOMAIN_TRANSITION_ADMISSION_ENABLED.
    • Review the remaining keys. Every key now reaches the server, including keys the old Compose file ignored.
  4. Download the new Compose file, pull, and restart, as in Update Or Roll Back.

Expect Traefik’s default certificate on every Buzz hostname until Buzz issues its wildcard, usually a few minutes. Activated custom hostnames answer 421 for up to about 90 seconds after the restart while Buzz checks them again, then get new production certificates from letsencrypt. The old acme.json in buzz_traefik-certs is no longer read. Delete it once you no longer need to roll back.

If you skip the upgrade, the old Compose file keeps working. Bumping only the image tag in the old file leaves the server container unhealthy, because the old healthcheck calls uv, which the image no longer ships, so replace the whole file. Leaving CF_API_TOKEN unrenamed makes Compose refuse to start.

To roll back, copy the saved docker-compose.yml, .env, and compose.override.yml back and run docker compose up -d --remove-orphans. The old Traefik serves its wildcard from acme.json again at once, and data written since the upgrade is kept.