Ctrl K
ComunidadGuías…

docker-compose-release.yml

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

Archivo original ↗Preguntar a KiBot
# Run a published Cloud Codex release without a build toolchain.
#
# This is the evaluation path: `docker compose -f docker-compose-release.yml up`
# pulls a prebuilt image instead of compiling the frontend locally, so trying
# Cloud Codex needs Docker and a .env, nothing else.
#
# Use docker-compose-prod.yml instead when you are running modified source:
# that file builds ./cloudcodex, this one ignores your working tree entirely.

services:
  database:
    image: mysql:8.4.11
    container_name: cloudcodex-db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASS}
    env_file:
      - .env
    # Deliberately NOT published to the host. The app reaches MySQL as
    # DB_HOST: database over the compose network, so publishing 3306 only
    # exposes the database, and Docker's published ports are a DNAT rule that
    # sits in front of the host firewall, so `ufw deny 3306` does not close it.
    # Use `docker compose exec database mysql ...` for a shell.
    expose:
      - "3306"
    volumes:
      - db_data:/var/lib/mysql
      # init.sql runs only when this volume is created empty. On an existing
      # volume Docker skips docker-entrypoint-initdb.d entirely, so schema
      # changes need a migrations/ file, applied with
      # `docker compose -f docker-compose-release.yml run --rm app npm run migrate`
      # (see the app service, which mounts migrations/ for exactly that).
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro,z
    # Over TCP, not the socket. On an empty volume the image first applies
    # init.sql on a temporary server with networking off, and a socket ping
    # answers that server: the app then started while nothing listened on
    # 3306 and exited on ECONNREFUSED until the restart policy recovered it.
    # Nothing answers 127.0.0.1 until the real server is up. start_period
    # covers the initialisation, so a slow first boot is not marked unhealthy:
    # the app waits on service_healthy, and an unhealthy database would stop
    # `up` outright. A passing ping ends the start period at once, so a
    # generous value costs a normal boot nothing.
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--protocol=tcp", "-u${DB_USER}", "-p${DB_PASS}"]
      interval: 7s
      timeout: 8s
      retries: 15
      start_period: 300s

  app:
    # Pin a release rather than tracking :latest, so an evaluator's install
    # does not change under them on the next publish. Override with
    # CLOUDCODEX_VERSION in .env to try a different release.
    #
    # UPGRADING IS NOT JUST THIS NUMBER. init.sql runs only on an empty data
    # directory, so a newer app against an older schema boots cleanly and then
    # 500s, and old code against a new schema is worse. The order is: pull the
    # new image, stop the app, migrate, then start it (upgrading from 0.11.0 or
    # earlier, `down` instead of `stop app`, because of the network pinned at
    # the end of this file):
    #
    #   docker compose -f docker-compose-release.yml pull app
    #   docker compose -f docker-compose-release.yml stop app
    #   docker compose -f docker-compose-release.yml run --rm app npm run migrate
    #   docker compose -f docker-compose-release.yml up -d
    #
    # `run --rm`, NOT `exec`. `exec` runs inside the container that is ALREADY
    # running, which on the upgrade that first ships the runner is the old one:
    # it has no scripts/migrate.js, no `migrate` script, and no /migrations
    # mount, because `docker compose pull` does not recreate a container. `run`
    # builds a one-off from the current compose file and the new image, so it
    # has all three, and it leaves the running app alone. 3306 is not published
    # here, so the runner runs in the compose network, not from the host.
    # See "Upgrades" in docs/deployment.md, which also covers the one-time
    # adoption step this database needs before its first ordinary run.
    # release.yml fails the build if this default falls behind the tag.
    image: ghcr.io/cloud-city-computing/cloud-codex:${CLOUDCODEX_VERSION:-0.15.0}
    container_name: cloudcodex-app
    restart: unless-stopped
    # Longer than the app's own ten-second shutdown bound (services/shutdown.js),
    # so it flushes open documents and exits before Docker's SIGKILL. Docker's
    # default is ten seconds, which would tie with it.
    stop_grace_period: 20s
    depends_on:
      database:
        condition: service_healthy
    # Both sides follow PORT so setting it in .env moves the published mapping
    # too. Without this the app would bind PORT inside the container while
    # Compose still published 3000, and every request to the host would be
    # refused while the logs showed a clean boot. `:-` covers the blank PORT=
    # and APP_BIND= that .env.example ships.
    #
    # The host side is 127.0.0.1 unless APP_BIND says otherwise, so only this
    # machine reaches the app: a browser on it for an evaluation, or the
    # TLS-terminating reverse proxy production belongs behind. Docker's
    # published ports are a DNAT rule in front of the host firewall, so a
    # mapping with no host address is reachable from anywhere that can route
    # here, past `ufw deny`. Set APP_BIND (0.0.0.0, or one interface's IPv4
    # address) only to expose the port on purpose; see "TLS and reverse proxy"
    # in docs/deployment.md. Never `::` or an IPv6 address: the network below
    # is IPv4-only, so Docker would carry every IPv6 client in as its gateway,
    # which TRUST_PROXY trusts.
    ports:
      - "${APP_BIND:-127.0.0.1}:${PORT:-3000}:${PORT:-3000}"
    env_file:
      - .env
    environment:
      DB_HOST: database
      # The image runs `node server.js` directly, so NODE_ENV comes from its ENV
      # alone, and a blank `NODE_ENV=` in .env (older .env.example files ship
      # one) would replace it with an empty value. environment wins over env_file.
      NODE_ENV: production
    volumes:
      # Uploaded avatars and images extracted out of documents live on disk
      # here, and only the path to them lives in MySQL: routes/helpers/images.js
      # replaces the base64 data URI in html_content with a /doc-images/ URL, so
      # after extraction the file IS the only copy. Without this volume, pulling
      # a newer image recreates the container and every avatar and pasted image
      # is gone for good while the database still points at it.
      - app_public:/app/public
      # The migration runner reads this. scripts/migrate.js ships in the image
      # (it is inside the cloudcodex/ build context) but migrations/ is at the
      # repo root and is not, so without this mount `npm run migrate` fails on a
      # missing directory. /migrations is where scripts/migrate.js resolves it
      # from WORKDIR /app.
      #
      # `ro,z`: read-only, because the runner never writes here, and `z` because
      # on an SELinux host (Fedora, RHEL, CentOS, Rocky) a bind mount with no
      # relabel flag is unreadable inside the container. Measured there:
      #   docker run --rm -v ./migrations:/migrations:ro node:20-slim ls /migrations
      #   ls: cannot open directory '/migrations': Permission denied
      # Lowercase `z` (shared label), not `Z` (private): more than one container
      # reads this directory, and `Z` would relabel it for one of them only.
      # docker-compose.linux.yml adds :Z for the DEV file's mounts; it does not
      # cover this file, so the flag belongs inline here.
      - ./migrations:/migrations:ro,z

volumes:
  db_data:
  app_public:

# The project's default network, pinned so its gateway is an address
# TRUST_PROXY's default names (172.29.0.1/32). A proxy on this host reaches the
# app through the published port, and Docker presents that traffic to the
# container as this gateway. Left to Docker, the subnet is whatever is free
# (172.18.0.0/16 on a fresh host, a 192.168 range once about fifteen networks
# exist), the gateway is not in the default, and every client behind the proxy
# would share one rate-limit bucket. 172.29.0.0/16 is distinct from Cloud
# 172.28.0.0/16, a subnet a sibling deployment on the same host may use, so both can run there. An install created
# before this was pinned has its network on another subnet, and its upgrade
# must take the project `down` (never `down -v`) BEFORE migrating: stopping
# only the app and then migrating makes Compose replace this network under a
# running database, which it reconnects without its `database` alias, so the
# migration and the app both fail to find it. See "Upgrades" in
# docs/deployment.md.
networks:
  default:
    ipam:
      config:
        - subnet: 172.29.0.0/16
          gateway: 172.29.0.1