Run a Server on Coolify

Deploy one Zugoid Server in Coolify from the published image, claim the Owner, add addresses and update by redeploying or pinning a version.

The Coolify template runs one standalone Zugoid Server: Zugoid on a single celld node with private RustFS storage. Coolify's proxy owns the domain and TLS. The template uses the published ghcr.io/inchidev/zugoid image. It is public, so Coolify pulls it without a registry login and nothing is built on your machine.

It is not a published Coolify catalog entry yet. Copy the Compose file below.

Install

  1. In Coolify, choose New Resource → Docker Compose and paste the Compose file.
  2. Set the domain of the zugoid service, on port 8080. Set ADMIN_USERNAME to the first Owner's username; it defaults to owner. Leave ZUGOID_VERSION unset to run latest.
  3. Deploy. The first start takes a few minutes while the database migrations are applied.
  4. Wait until https://<your-domain>/api/setup/health answers with HTTP 200.
  5. Sign in with ADMIN_USERNAME and the password Coolify generated: SERVICE_PASSWORD_ADMIN in the service's environment variables. Then change the password in your account settings.

The first start creates the Owner from these values. Later changes to SERVICE_PASSWORD_ADMIN have no effect. To choose the password yourself, set SERVICE_PASSWORD_ADMIN (at least 12 characters) before the first deploy.

If the Server does not come up, read the logs of the zugoid container in Coolify; see also Troubleshooting.

Coolify generates these values for the template:

Variable Used as
SERVICE_URL_ZUGOID_8080, SERVICE_FQDN_ZUGOID_8080 The Server's public origin (PUBLIC_ORIGIN).
SERVICE_PASSWORD_64_AUTHSECRET AUTH_SECRET. Signs sessions and encrypts stored credentials.
SERVICE_PASSWORD_ADMIN ADMIN_PASSWORD. The first Owner's initial password.
SERVICE_PASSWORD_64_SETUPTOKEN SETUP_TOKEN. Only used if the Owner is claimed on /setup instead.
SERVICE_USER_RUSTFS, SERVICE_PASSWORD_64_RUSTFS Keys for the private storage bucket.

Important

Keep these values private and never regenerate them. A new AUTH_SECRET signs out everyone and makes stored credentials unreadable.

APP_NAME sets the initial name (default Zugoid). You can rename the Server later in Settings.

Compose file

This is self-host/coolify/docker-compose.yaml as of this release. Use Copy and paste it into Coolify unchanged.

# documentation: https://github.com/inchidev/zugoid/blob/main/self-host/coolify/README.md
# slogan: Self-hosted team chat on a single VPS.
# category: collaboration
# tags: chat,team,celld,rustfs,self-host,zugoid
# logo: svgs/zugoid.png
# port: 8080

services:
  # Existing installations used a root-owned volume. Prepare it before the
  # unprivileged node starts, including after a cold restore.
  celld-perms:
    image: alpine:3.24
    user: "0:0"
    restart: "no"
    network_mode: none
    read_only: true
    cap_drop: [ALL]
    cap_add: [CHOWN, DAC_OVERRIDE, FOWNER]
    security_opt: [no-new-privileges:true]
    volumes:
      - celld-data:/data
    command: ["chown", "-R", "10001:10001", "/data"]

  rustfs-perms:
    image: alpine:3.24
    user: "0:0"
    restart: "no"
    volumes:
      - rustfs-data:/data
    command:
      - sh
      - -c
      - chown -R 10001:10001 /data

  rustfs:
    image: rustfs/rustfs:1.0.4
    restart: always
    depends_on:
      rustfs-perms:
        condition: service_completed_successfully
    environment:
      - RUSTFS_VOLUMES=/data
      - RUSTFS_ADDRESS=0.0.0.0:9000
      - RUSTFS_CONSOLE_ENABLE=false
      - RUSTFS_ACCESS_KEY=$SERVICE_USER_RUSTFS
      - RUSTFS_SECRET_KEY=$SERVICE_PASSWORD_64_RUSTFS
      - RUSTFS_OBS_LOGGER_LEVEL=error
    volumes:
      - rustfs-data:/data
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://127.0.0.1:9000/health"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 20s

  bucket-init:
    image: amazon/aws-cli:2.39.3
    restart: "no"
    depends_on:
      rustfs:
        condition: service_healthy
    environment:
      - AWS_ACCESS_KEY_ID=$SERVICE_USER_RUSTFS
      - AWS_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_64_RUSTFS
      - AWS_DEFAULT_REGION=${AWS_REGION:-us-east-1}
      - AWS_EC2_METADATA_DISABLED=true
    entrypoint: ["/bin/sh", "-c"]
    command:
      - |
        set -eu
        aws configure set default.s3.addressing_style path
        i=0
        while [ "$$i" -lt 30 ]; do
          if aws s3api head-bucket --bucket cells --endpoint-url http://rustfs:9000 >/dev/null 2>&1; then
            exit 0
          fi
          if aws s3api create-bucket --bucket cells --endpoint-url http://rustfs:9000 --region us-east-1; then
            exit 0
          fi
          i=$$((i + 1))
          sleep 2
        done
        echo "could not create s3://cells" >&2
        exit 1

  # Updates are this image tag: latest follows stable releases, or pin a
  # version such as 0.0.2 and change it to update. Never set an older one.
  zugoid:
    image: ghcr.io/inchidev/zugoid:${ZUGOID_VERSION:-latest}
    # A celld node that loses its lease exits; Docker starts it again.
    restart: always
    user: "10001:10001"
    read_only: true
    cap_drop: [ALL]
    security_opt: [no-new-privileges:true]
    tmpfs:
      - /tmp:size=256m,mode=1777
    stop_grace_period: 50s
    depends_on:
      celld-perms:
        condition: service_completed_successfully
      bucket-init:
        condition: service_completed_successfully
      rustfs:
        condition: service_healthy
    environment:
      - SERVICE_URL_ZUGOID_8080
      - SERVICE_FQDN_ZUGOID_8080
      - PUBLIC_ORIGIN=$SERVICE_URL_ZUGOID_8080
      - ZUGOID_ORIGINS=${ZUGOID_ORIGINS:-}
      - AUTH_SECRET=$SERVICE_PASSWORD_64_AUTHSECRET
      - SETUP_TOKEN=$SERVICE_PASSWORD_64_SETUPTOKEN
      - ADMIN_USERNAME=${ADMIN_USERNAME:-owner}
      # Coolify generates the first Owner's password; the first start creates the Owner.
      - ADMIN_PASSWORD=$SERVICE_PASSWORD_ADMIN
      - APP_NAME=${APP_NAME:-Zugoid}
      - AWS_ACCESS_KEY_ID=$SERVICE_USER_RUSTFS
      - AWS_SECRET_ACCESS_KEY=$SERVICE_PASSWORD_64_RUSTFS
      - AWS_REGION=${AWS_REGION:-us-east-1}
      - AWS_EC2_METADATA_DISABLED=true
      - S3_ENDPOINT=http://rustfs:9000
      - CELLD_BUCKET=s3://cells
      - CELLD_WATCH=/var/lib/zugoid/celld
      - CELLD_DURABILITY=bucket
      - CELLD_READY_FLEET_GATE_MS=0
      - CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID:-}
      - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:-}
      - AI_BASE_URL=${AI_BASE_URL:-}
      - AI_API_KEY=${AI_API_KEY:-}
      - AI_MODEL=${AI_MODEL:-}
      - MAIL_GATEWAY_URL=${MAIL_GATEWAY_URL:-}
      - MAIL_GATEWAY_TOKEN=${MAIL_GATEWAY_TOKEN:-}
      - SMTP_GATEWAY_URL=${SMTP_GATEWAY_URL:-}
      - SMTP_GATEWAY_TOKEN=${SMTP_GATEWAY_TOKEN:-}
      - ZUGOID_EMAIL_DOMAINS=${ZUGOID_EMAIL_DOMAINS:-[]}
      - MAIL_APP_HOSTNAME=${MAIL_APP_HOSTNAME:-}
      - MAIL_DEFAULT_LOCAL_PART=${MAIL_DEFAULT_LOCAL_PART:-}
      - INBOUND_EMAIL_SECRET=${INBOUND_EMAIL_SECRET:-}
      - BROWSER_CDP_URL=${BROWSER_CDP_URL:-}
    volumes:
      - celld-data:/var/lib/zugoid/celld
    healthcheck:
      test: ["CMD", "/usr/local/bin/zugoid-healthcheck"]
      interval: 15s
      timeout: 5s
      retries: 12
      start_period: 240s

volumes:
  rustfs-data:
  celld-data:

More addresses

To serve more domains, add them in Coolify and list every origin in ZUGOID_ORIGINS, comma-separated. The Server trusts only listed origins. RustFS has no public port.

Updates

The image is built for linux/amd64 and linux/arm64. Only stable releases are published as images, with three tags: the exact version (for example 0.3.5), its minor line (0.3) and latest.

Updates are controlled in Coolify, not in Zugoid:

  • Follow stable releases. Leave ZUGOID_VERSION unset to use latest. Redeploy the service to pull the newest stable release.
  • Pin a version. Set ZUGOID_VERSION to an exact version. Change it and redeploy to update.

On start, the new version applies its database migrations and skips ones already applied. Migrations only go forward: an older image refuses to start on data a newer one has migrated. If that happens, set a version at least as new as the one in the error.

In Zugoid, Server settings → Deployment → Updates shows "Updates are managed outside Zugoid" and tells you when a newer stable release exists.

Docker and Coolify Servers update outside Zugoid

Backups

Backups are optional but recommended before an update.

  • Server settings → Deployment → Backups downloads the database and files, or sends them to an S3 destination.
  • For a full copy, stop the service and archive the rustfs-data and celld-data volumes. A copy taken while the service runs may be inconsistent.

Keep backups outside these volumes. See Files and backups.

Optional providers

Cloudflare, AI and mail credentials are optional. The template accepts CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, AI_BASE_URL, AI_API_KEY, AI_MODEL, MAIL_GATEWAY_URL, MAIL_GATEWAY_TOKEN, SMTP_GATEWAY_URL, SMTP_GATEWAY_TOKEN, ZUGOID_EMAIL_DOMAINS, MAIL_APP_HOSTNAME, MAIL_DEFAULT_LOCAL_PART, INBOUND_EMAIL_SECRET and BROWSER_CDP_URL. See Optional providers for what each one does.