Skip to content

Deploy With Coolify

Deploy Buzz as a Docker Compose application while Coolify manages the host and Traefik proxy. A deployment repository you control holds the Compose file, which connects Buzz to the external coolify network and stores data in buzz_buzz-data.

Buzz needs no proxy changes. It obtains the certificate for buzz.example.com and *.buzz.example.com itself and publishes it, together with any custom-domain routers, to /data/coolify/proxy/dynamic, which Coolify’s default proxy already watches.

You need:

  • A working Coolify installation with its default proxy enabled.
  • A git repository you control for the deployment, readable by Coolify.
  • The DNS zone of buzz.example.com hosted on Cloudflare, with the records from Configure DNS And TLS.
  • A Cloudflare API token with Zone:Read and DNS:Edit, scoped to that zone.
  • A configured GitHub OAuth app.

Complete the access decision in the Self-Hosting Overview before deployment. Configure upstream controls first if Buzz is for a closed group.

  1. Create a deployment repository containing this Compose file as docker-compose.yml. It runs ghcr.io/infomiho/buzzstatic pinned to the current release:

    docker-compose.yml
    services:
    server:
    image: ghcr.io/infomiho/buzzstatic:0.7.1 # x-release-please-version
    restart: unless-stopped
    environment:
    - BUZZ_DATA_DIR=/data
    - BUZZ_DOMAIN=${BUZZ_DOMAIN}
    - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
    - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
    - BUZZ_ANALYTICS_SECRET=${BUZZ_ANALYTICS_SECRET:-}
    - BUZZ_MAX_ARCHIVE_BYTES=${BUZZ_MAX_ARCHIVE_BYTES:-524288000}
    - BUZZ_MAX_SITE_BYTES=${BUZZ_MAX_SITE_BYTES:-524288000}
    - BUZZ_MAX_SITE_FILES=${BUZZ_MAX_SITE_FILES:-10000}
    - BUZZ_MAX_ARCHIVE_PATH_BYTES=${BUZZ_MAX_ARCHIVE_PATH_BYTES:-1024}
    - BUZZ_ALLOW_REGISTRATION=${BUZZ_ALLOW_REGISTRATION:-true}
    - BUZZ_ALLOWED_GITHUB_USERS=${BUZZ_ALLOWED_GITHUB_USERS:-}
    - BUZZ_TRAEFIK_DYNAMIC_DIR=/traefik-dynamic
    - BUZZ_CLOUDFLARE_DNS_API_TOKEN=${BUZZ_CLOUDFLARE_DNS_API_TOKEN}
    - BUZZ_ACME_DIRECTORY_URL=${BUZZ_ACME_DIRECTORY_URL:-}
    - BUZZ_CUSTOM_DOMAINS_ENABLED=${BUZZ_CUSTOM_DOMAINS_ENABLED:-false}
    - BUZZ_TRAEFIK_HTTPS_ENTRYPOINT=https
    - BUZZ_CUSTOM_DOMAIN_OPERATOR_TOKEN=${BUZZ_CUSTOM_DOMAIN_OPERATOR_TOKEN:-}
    - BUZZ_MAX_CUSTOM_DOMAINS_PER_SITE=${BUZZ_MAX_CUSTOM_DOMAINS_PER_SITE:-5}
    - BUZZ_MAX_CUSTOM_DOMAINS_PER_USER=${BUZZ_MAX_CUSTOM_DOMAINS_PER_USER:-20}
    - BUZZ_MAX_CUSTOM_DOMAINS_SERVER_WIDE=${BUZZ_MAX_CUSTOM_DOMAINS_SERVER_WIDE:-1000}
    - BUZZ_CUSTOM_DOMAIN_INGRESS_IPS=${BUZZ_CUSTOM_DOMAIN_INGRESS_IPS:-}
    - BUZZ_CUSTOM_DOMAIN_ORIGIN_HOST=${BUZZ_CUSTOM_DOMAIN_ORIGIN_HOST:-coolify-proxy}
    - BUZZ_TRAEFIK_CERT_RESOLVER=${BUZZ_TRAEFIK_CERT_RESOLVER:-letsencrypt}
    - BUZZ_CUSTOM_DOMAIN_RECONCILE_SECONDS=${BUZZ_CUSTOM_DOMAIN_RECONCILE_SECONDS:-5}
    volumes:
    - buzz-data:/data
    - /data/coolify/proxy/dynamic:/traefik-dynamic
    expose:
    - "8080"
    - "8081"
    healthcheck:
    test: ["CMD", "/app/.venv/bin/python", "-m", "server.healthcheck"]
    interval: 30s
    timeout: 5s
    start_period: 10s
    retries: 3
    networks:
    - coolify
    labels:
    - traefik.enable=true
    - "traefik.http.routers.buzz.rule=Host(`${BUZZ_DOMAIN}`)"
    - traefik.http.routers.buzz.entrypoints=https
    - traefik.http.routers.buzz.tls=true
    - "traefik.http.routers.buzz-wildcard.rule=HostRegexp(`.+\\.${BUZZ_DOMAIN}`)"
    - traefik.http.routers.buzz-wildcard.entrypoints=https
    - traefik.http.routers.buzz-wildcard.tls=true
    - traefik.http.services.buzz.loadbalancer.server.port=8080
    networks:
    coolify:
    external: true
    volumes:
    buzz-data:
    name: buzz_buzz-data
  2. Create a Docker Compose application from your deployment repository with the Compose file set to docker-compose.yml.

  3. Enable Raw Docker Compose Deployment in the application settings. The Compose file supplies the routing labels directly. Coolify documents this mode in Raw Docker Compose Deployment.

  4. Leave FQDN/Domains empty. The Compose labels define both the base and wildcard routes.

  5. Add these environment variables:

    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

    BUZZ_DOMAIN must be a bare hostname. Buzz exits at startup when it is missing or contains a scheme, port, or path. The Compose file passes the other settings it lists from the application’s environment, as described in the configuration reference.

  1. Deploy the application through Coolify.

  2. Confirm that the application is running and healthy. The Compose file expects Coolify’s external Docker network to be named coolify. If deployment reports that this network doesn’t exist, confirm the server’s network name before changing the Compose file.

  3. Open the application logs. The server must report no ERROR: line, and after the first issuance it logs Wildcard certificate issued, valid until with the expiry date.

  4. Request the health endpoint:

    Terminal window
    curl --fail --show-error https://buzz.example.com/health
  5. Run the certificate inspection command in Verify DNS, Routing, And TLS and confirm that the certificate covers *.buzz.example.com. The first issuance takes up to a few minutes. Until then the proxy serves its default certificate and /health reports "status": "degraded". Buzz checks the served certificate again a few seconds after it publishes one.

See Troubleshoot Self-Hosting for routing and certificate failures.

The Compose file bind-mounts /data/coolify/proxy/dynamic at /traefik-dynamic and sets BUZZ_TRAEFIK_DYNAMIC_DIR to it. Buzz keeps the certificate, its key, and the ACME account key in /data/tls. It writes the certificate to buzz-certificate.yaml in the dynamic directory and checks that file every five minutes, rewriting it when it is missing or changed without placing a new order. Buzz renews when a third of the certificate’s lifetime remains. The Buzz routers set tls=true without a certificate resolver.

Buzz also requests a random site hostname through coolify-proxy:443 every 5 minutes. When that certificate is invalid or expires within 21 days, /health reports "status": "degraded" and the dashboard shows a warning. /health is also degraded while Buzz cannot obtain, save, or publish the certificate. wildcard_certificate_error then holds a short code, such as not_configured when BUZZ_CLOUDFLARE_DNS_API_TOKEN is missing or issuance_failed, and the application logs hold the detail. After a failure Buzz retries after 2, 5, 15, and 30 minutes, then every 30 minutes. /health keeps returning HTTP 200, and sites keep being served. Monitor the status field rather than the exact response body.

Keep Buzz the only source of this certificate. A proxy-issued wildcard for the same names makes Traefik pick either certificate at random after a restart, and nothing renews the proxy’s copy.

Run one Buzz application per Coolify server. The file names in the dynamic directory, the Traefik router names, and the buzz_buzz-data volume name are fixed, so a second instance on the same proxy overwrites the first one’s certificate and routes.

The certificate’s private key is part of buzz-certificate.yaml, so Coolify users who can manage the server see it under Servers > Proxy > Dynamic Configurations. Other team members see only the file names. Users who can manage the server can also read the proxy’s acme.json and run commands there, so the key adds no new exposure. Treat that access as access to the certificate.

Buzz works with Coolify’s default proxy configuration, so Reset configuration to default in Servers > Proxy is always safe for Buzz. When the proxy fails to restart or another application loses TLS, reset it and restart the proxy.

Do not add Traefik dashboard or API labels, such as a router with service=api@internal, to the proxy. Buzz doesn’t need them, and on the shared coolify network they expose Traefik’s runtime state.

Buzz writes only buzz-certificate.yaml and buzz-routes.yaml, each validated and replaced atomically, and never touches other files in /data/coolify/proxy/dynamic. One malformed file anywhere in that directory still stops Traefik from loading changes to every file there, and a proxy restart then drops all of their routes and certificates, including Coolify’s own, until the file is fixed or removed. Coolify lists these files under Servers > Proxy > Dynamic Configurations. Do not edit Buzz’s files there.

Custom domains are disabled by default and need no proxy changes. Skip this section when the operator does not want Buzz to manage custom domains.

  1. Add these variables to the Buzz application and redeploy it:

    BUZZ_CUSTOM_DOMAINS_ENABLED=true
    BUZZ_CUSTOM_DOMAIN_INGRESS_IPS=your-public-ingress-ip

    List every public IPv4 or IPv6 address that reaches this proxy, separated by commas. Every DNS answer for a direct custom hostname must be public and present in this list.

  2. Open the site detail page of any site. The custom-domain section reports whether the control plane is ready.

  3. Add a custom hostname, publish the displayed DNS records, and select Check ownership.

After ownership verifies, Buzz adds one generation-qualified Host() router to buzz-routes.yaml. Each version of that file carries a revision router on the reserved hostname buzz-config-revision.invalid, and Buzz treats a change as applied only once a probe through coolify-proxy:443 shows Traefik serving that revision. Buzz needs no access to Traefik’s API.

Traefik requests each custom hostname’s certificate over HTTP-01 through letsencrypt, Coolify’s default resolver. If your letsencrypt resolver uses DNS-01, add a separate HTTP-01 resolver with its own ACME storage file in Servers > Proxy and set BUZZ_TRAEFIK_CERT_RESOLVER to its name. A DNS-01 resolver cannot issue certificates for customer zones.

BUZZ_MAX_CUSTOM_DOMAINS_PER_SITE, BUZZ_MAX_CUSTOM_DOMAINS_PER_USER, and BUZZ_MAX_CUSTOM_DOMAINS_SERVER_WIDE limit pending and verified hostnames. A hostname awaiting withdrawal still counts.

Onboarding is automatic. Buzz detects whether a hostname points directly at the ingress addresses or through Cloudflare and switches when DNS changes. Site owners must keep the TXT ownership record, enable orange-cloud proxying, and select Cloudflare Full (strict). Bypass cache, redirects, WAF, Workers, Access, and managed challenges for /.well-known/buzz-domain-check/* and /.well-known/acme-challenge/*.

Buzz uses no customer Cloudflare credentials. It validates resolved addresses against a bundled snapshot of Cloudflare’s ranges. The snapshot fails closed after 180 days. Upgrade Buzz to refresh it.

Set BUZZ_CUSTOM_DOMAIN_OPERATOR_TOKEN to one or more bearer tokens separated by commas. Buzz then listens on the private port 8081:

  • GET /operator/domain-transitions reports active DNS path transitions with their current evidence.
  • POST /operator/domain-transitions/{claim_id}/cancel cancels a transition. It returns 404 for an unknown claim and 409 when cancelling is unsafe.

Do not route this port through the proxy.

Set BUZZ_CUSTOM_DOMAINS_ENABLED=false and redeploy. Custom hostnames stop serving immediately. Buzz rewrites buzz-routes.yaml without their routers and logs All custom-domain routers are withdrawn once Traefik serves that revision. Keep BUZZ_TRAEFIK_DYNAMIC_DIR and the bind mount: without them Buzz cannot withdraw the routers, so it serves no custom hostname and /health reports custom_domains_error: routers_unmanaged.

To update, create a cold backup and read the release notes for the versions you skip past. Then replace docker-compose.yml with the file from this page for the new release, push, and redeploy the application in Coolify.

Replace the whole file rather than only the image: tag, because the image, healthcheck, mounts, and environment change together. A 0.6 Compose file with only its tag bumped leaves the container unhealthy, because its healthcheck calls uv, which the image no longer ships.

Every redeploy interrupts sites for a few seconds. When a custom hostname’s router or certificate changes, it can answer 421 for up to about 90 seconds while Buzz checks it again.

Database migrations run automatically when the new server starts and are forward-only: an older image refuses to start against the migrated database. Before migrating, Buzz saves the database to /data/backups. To roll back, set docker-compose.yml back to the previous release, restore the pre-migration backup named in the refusal message, and redeploy.

Earlier releases asked you to edit the proxy: a wildcard resolver, and for custom domains an HTTP provider, the Traefik API, and extra resolvers. This release replaces all of that. Do these steps in one maintenance window, in order. Sites are down for about 20 seconds in total, and the wildcard certificate stays valid throughout.

  1. Create a cold backup and copy the complete configuration shown under Servers > Proxy to secure off-host storage. That copy is your proxy rollback. Coolify also saves a copy in /data/coolify/proxy/backups on every save and reset.
  2. Add BUZZ_CLOUDFLARE_DNS_API_TOKEN to the application’s environment variables. Without the token Buzz doesn’t issue a certificate, /health reports not_configured, and you must stop after step 5.
  3. Keep BUZZ_TRAEFIK_CERT_RESOLVER as it is until step 8. The new default, letsencrypt, may still be DNS-01 on your proxy, and a DNS-01 resolver cannot issue certificates for custom hostnames.
  4. Confirm that BUZZ_DOMAIN is a bare hostname such as buzz.example.com. Otherwise the new server exits at startup and every site goes down.
  1. Replace docker-compose.yml in your deployment repository with the file from this page, keeping any local additions, then push and redeploy. Sites are down for about 7 seconds, and the proxy switches to Buzz’s certificate without a TLS gap.

    Then confirm that /data/coolify/proxy/dynamic/buzz-certificate.yaml exists on the Coolify host and that /health reports "wildcard_certificate_error": null. Do not continue otherwise: resetting the proxy without Buzz’s certificate drops TLS on every Buzz host.

    Until step 9, Traefik logs a provider error for buzz-traefik-control every few seconds. That is expected.

  1. Check that every other application on this server is reachable on port 80. Reset returns the letsencrypt resolver to HTTP-01, which those applications then renew with.

  2. Open Servers > Proxy and select Reset configuration to default. Coolify saves the default configuration immediately and keeps running the old one until the proxy restarts. The default has no cloudflare, buzz-custom, or buzz-production resolver, no CF_DNS_API_TOKEN, no --api=true, buzz-admin, or --providers.http flags, and no wildcard-certs or api@internal labels. Check the saved configuration for them.

    If another application cannot use HTTP-01, skip Reset and delete only those Buzz items by hand, keeping your DNS-01 letsencrypt and its token. Then add a separate HTTP-01 resolver for custom domains as described in Enable Custom Domains.

  3. Delete the old wildcard and the old resolver setting:

    • Under Servers > Proxy > TLS Certificates, delete the entry for buzz.example.com and *.buzz.example.com, if there is one.
    • Leave /data/coolify/proxy/acme-cloudflare.json in place if you used the separate cloudflare resolver. Traefik ignores it without that resolver, and keeping it makes a rollback free of TLS gaps. Delete it once you no longer need to roll back.
    • Delete BUZZ_TRAEFIK_CERT_RESOLVER from the application, unless it names an HTTP-01 resolver that still exists.
  4. Restart the proxy, then immediately redeploy the application. Sites are down for about 3 seconds and then about 7 seconds. Custom hostnames show the default certificate and then answer 421 for up to about 90 seconds while letsencrypt issues their certificates and Buzz checks them again.

  5. Remove these application environment variables. No redeploy is needed. Coolify adds BUZZ_TRAEFIK_CERT_RESOLVER=letsencrypt back from the Compose file, which is correct.

    • BUZZ_TRAEFIK_CONTROL_TOKEN, BUZZ_TRAEFIK_CONTROL_PORT, BUZZ_TRAEFIK_API_URL, and BUZZ_TRAEFIK_API_AUTHORIZATION
    • BUZZ_CUSTOM_DOMAIN_ACME_CA_SERVER, 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

    Save BUZZ_TRAEFIK_CONTROL_TOKEN with the proxy copy first if you may roll back.

  1. On the Coolify host, compare the serial the proxy serves with the one Buzz stored:

    Terminal window
    openssl s_client -connect buzz.example.com:443 -servername check.buzz.example.com </dev/null 2>/dev/null | openssl x509 -noout -serial
    docker run --rm -v buzz_buzz-data:/data:ro python:3.12-slim python -c "import json; print(json.load(open('/data/tls/wildcard-certificate.json'))['chain'])" | openssl x509 -noout -serial

    Both lines must match. With custom domains enabled, also open a site detail page and confirm that the control plane is ready.

  1. Add back the variables the old release needs: BUZZ_TRAEFIK_CONTROL_TOKEN with the value from the saved proxy configuration, and BUZZ_TRAEFIK_CERT_RESOLVER with its previous value.
  2. Paste the saved proxy configuration into Servers > Proxy, save it, and restart the proxy. Buzz’s certificate keeps being served.
  3. Restore the previous docker-compose.yml, push, and redeploy.
  4. Delete buzz-certificate.yaml and buzz-routes.yaml from /data/coolify/proxy/dynamic. Otherwise stale routes and the random certificate pick return.

Traefik orders its own wildcard only once Buzz’s file is gone, so sites show the default certificate until that order completes, a few seconds in rehearsal and longer with Let’s Encrypt and Cloudflare. With the separate cloudflare resolver and acme-cloudflare.json kept, Traefik serves the old certificate again without a gap.