Ctrl K
ComunidadGuías…

docker-compose.yml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Zoomies on a single VM, behind Cloudflare.
#
#   1. cp .env.example .env  and fill in the three values it asks for:
#        ZOOMIES_EXTERNAL_URL    the https address GitHub and you reach this at
#        ZOOMIES_ENCRYPTION_KEY  openssl rand -base64 32
#        DOCKER_GID              stat -c '%g' /var/run/docker.sock
#   2. mkdir -p data && sudo chown 65532:65532 data
#      sudo mkdir -p /var/lib/zoomies/shared && sudo chown 65532:65532 /var/lib/zoomies/shared
#   3. docker compose up -d
#   4. docker compose logs zoomies | grep 'setup token'
#   5. open ZOOMIES_EXTERNAL_URL, paste that token, and create the first admin
#
# Step 4 is the gate on step 5. This origin is reachable the moment the
# container starts, and an empty database is not proof of ownership: the setup
# token, which only somebody who can read this container's log has, is. It
# changes on every restart and stops being printed once an account exists.
#
# Steps 4 and 5 are for a person. Where nobody is watching -- this file driven
# by a Terraform module or a configuration-management run -- set the
# ZOOMIES_BOOTSTRAP_* lines and the secrets block below instead: the first
# account is created from a file at startup, /readyz reports
# bootstrap_required false once it exists, and nothing has to be read out of
# the log. docs/configuration.md has the details.
#
# Compose refuses to start with any of the three missing, on purpose: each one
# left to a default produces a container that is healthy and useless. A wrong
# external URL sends GitHub's webhooks and its App-creation redirect to the
# wrong site; a missing key means nothing secret can be stored; and a wrong
# DOCKER_GID leaves the agent unable to open the socket, so every pool queues
# for ever. The Hosts page diagnoses that last one and names the line to
# change, but it is quicker not to have to.
#
# Step 2 is not optional, and the chown needs root: handing a directory to
# another uid is not something the docker group lets you do. The database
# lives in ./data next to this file, bind
# mounted into the container, and a bind mount keeps the host directory's
# ownership -- unlike a named volume, it does not inherit anything from the
# image. The container runs as uid 65532, so a ./data owned by anyone else (and
# Docker creates a missing one as root) gives you:
#
#   store: connecting to /var/lib/zoomies/zoomies.db: unable to open database
#   file (14)
#
# Cloudflare terminates TLS and proxies to this origin over plain HTTP on port
# 80, so the container serves HTTP and Zoomies is told its external URL is
# https. That is what makes the session cookie Secure, the webhook URL correct,
# and the UI's links right.
#
# The controller runs an embedded agent, so this is one container plus the
# runners it creates.

name: zoomies

services:
  zoomies:
    image: ghcr.io/eyupio/zoomies:latest
    # Or build locally:
    # build:
    #   context: .
    #   dockerfile: deploy/Dockerfile
    #   target: controller
    restart: unless-stopped
    # Finish admitted runner creation before replacing the service.
    stop_grace_period: 20m
    # The embedded agent registers this host under the container's hostname.
    # Without one that is the container's random twelve-hex-digit ID, which
    # then follows the host into every scheduler reason and problem message.
    hostname: zoomies
    command: ["controller"]
    # Compose already reads .env to interpolate the ${...} below; this passes it
    # into the container as well, so any of the ZOOMIES_* settings in
    # docs/configuration.md can be set by putting it in .env, with no matching
    # line needed here. The explicit environment: entries below still win.
    #
    # required: false keeps a deployment that supplies its own environment --
    # systemd, a secrets manager, CI -- from failing on a missing .env. It needs
    # Compose v2.24 or newer; on older versions use `env_file: .env`.
    env_file:
      - path: .env
        required: false
    environment:
      # How GitHub and your browser reach this controller. Webhooks are
      # delivered to $ZOOMIES_EXTERNAL_URL/webhooks/github, the GitHub App
      # created from the Installations page redirects back to it, and the
      # https here is what makes Zoomies mark its session cookie Secure.
      ZOOMIES_EXTERNAL_URL: ${ZOOMIES_EXTERNAL_URL:?set this in .env to the https address this controller is reached at}

      # 32 bytes, base64. Generate with: openssl rand -base64 32
      # Back it up: without it the stored GitHub App key cannot be decrypted.
      ZOOMIES_ENCRYPTION_KEY: ${ZOOMIES_ENCRYPTION_KEY:?set this in .env}

      # Inside the container we bind to all interfaces; the published port
      # below is what actually controls exposure. TLS is off here because
      # Cloudflare terminates it -- Zoomies will say so at startup, and that
      # warning is expected in this deployment.
      ZOOMIES_BIND: "0.0.0.0:8080"
      ZOOMIES_TLS_MODE: "off"

      # Without this every audit row and every login rate-limit decision sees
      # Cloudflare's address instead of the real client's, so the whole
      # internet is throttled as one caller and the audit log is useless.
      # Refresh from https://www.cloudflare.com/ips/ when Cloudflare publishes
      # a change.
      ZOOMIES_TRUSTED_PROXIES: ${ZOOMIES_TRUSTED_PROXIES}

      ZOOMIES_DB_PATH: /var/lib/zoomies/zoomies.db
      ZOOMIES_AGENT_EMBEDDED: "true"
      ZOOMIES_AGENT_BACKEND: docker
      ZOOMIES_DOCKER_HOST: unix:///var/run/docker.sock
      # Empty on purpose: the agent works its own capacity out from the
      # machine it measures, which is the only figure here that has seen it. A
      # constant would pin every deployment of this file to the same number of
      # slots however large the host is -- and the capacity is no longer only
      # a slot count: a pool that leaves its runners' size to the host is
      # given the machine divided by it. Set this to override.
      ZOOMIES_AGENT_CAPACITY: ${ZOOMIES_AGENT_CAPACITY}
      # The network runner containers are attached to: the one declared at the
      # bottom of this file. Without this line the agent leaves them on the
      # daemon's default bridge, whatever the comment down there promises.
      ZOOMIES_AGENT_NETWORK: zoomies
      ZOOMIES_WORK_DIR: /var/lib/zoomies/work
      ZOOMIES_LOG_FORMAT: json

      # The gid group_add below is given, echoed into the container so that
      # when it is wrong the Hosts page can say "the container holds 999 and
      # the socket is owned by 987; change DOCKER_GID in .env" rather than
      # leaving you to work out which file the number came from.
      DOCKER_GID: ${DOCKER_GID:?set this in .env to the gid that owns /var/run/docker.sock, which stat -c %g /var/run/docker.sock prints}

      # The unattended first account. Unset, the setup token above is the way
      # in. Set the username and exactly one of the two files; each is read
      # once, on a database with no accounts, and ignored with a warning ever
      # after. The token file holds a token you generated
      # (openssl rand -hex 32) that becomes a platform API token; the password
      # file holds a password for a person's account instead.
      # ZOOMIES_BOOTSTRAP_ADMIN: platform
      # ZOOMIES_BOOTSTRAP_TOKEN_FILE: /run/secrets/zoomies_bootstrap_token
      # ZOOMIES_BOOTSTRAP_PASSWORD_FILE: /run/secrets/zoomies_bootstrap_password
    # The file behind it, if you set one. The long syntax matters: compose
    # mounts a secret 0444 by default, and Zoomies refuses a credential file
    # that group or other can read. The uid is the one the image runs as.
    # secrets:
    #   - source: zoomies_bootstrap_token
    #     uid: "65532"
    #     mode: 0400
    ports:
      # Cloudflare connects here over HTTP. Nothing but Cloudflare should be
      # able to: put the origin behind a firewall that allows only Cloudflare's
      # ranges, or use a Cloudflare Tunnel and publish no port at all.
      - "80:8080"
    volumes:
      # The database, in ./data beside this file -- so backing Zoomies up is
      # copying a directory, and inspecting it does not need `docker volume
      # inspect`. See step 2 above: this directory must be owned by 65532.
      # (Runner containers keep their scratch space in their own filesystem;
      # nothing of theirs lands here.)
      - type: bind
        source: ./data
        target: /var/lib/zoomies
        bind:
          # Compose would otherwise helpfully create a missing ./data -- as
          # root, since the daemon is root -- and hand you error 14 at startup.
          # Failing with "bind source path does not exist" is far easier to act
          # on than a SQLite error code.
          create_host_path: false
      # The shared folder: runners' caches, one folder per purpose, and where
      # later releases add folders of their own -- which is why it has to be
      # owned by 65532 (step 2), and why Zoomies creates what it needs inside
      # it as it starts. It is mounted at the path it has on the host, because
      # the host's Docker daemon binds its subfolders into runner containers,
      # and a path that meant something else inside this container would point
      # a runner at a folder the daemon cannot see.
      - type: bind
        source: /var/lib/zoomies/shared
        target: /var/lib/zoomies/shared
        bind:
          create_host_path: false
      # The embedded agent needs the Docker socket to create runner containers.
      # This is Zoomies' own access to Docker -- it is NOT the same thing as
      # pool.docker_mode=host-socket, which would hand the socket to your jobs.
      - /var/run/docker.sock:/var/run/docker.sock
    # The image runs as uid 65532; that user must be able to use the socket,
    # which means joining the group that owns it. That group is read from the
    # socket, not assumed to be called docker: it is 987 on one distribution,
    # 999 or 998 on another, and something else again for a rootless daemon.
    # A container is given its groups when it is created, so after changing
    # DOCKER_GID run `docker compose up -d` again and compose recreates it.
    group_add:
      - ${DOCKER_GID}
    networks: [zoomies]
    healthcheck:
      test: ["CMD", "/usr/local/bin/zoomies", "healthcheck", "--url", "http://127.0.0.1:8080"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s

# The secret the commented bootstrap lines above name, kept out of .env and out
# of `docker inspect`.
# secrets:
#   zoomies_bootstrap_token:
#     file: ./bootstrap-token

networks:
  # Runner containers are attached to this network by the embedded agent, which
  # keeps them off the host network and off the default bridge.
  zoomies:
    name: zoomies