Build:
  1. 1
  2. 0
2026-07-09 16:05.21: New job: ocaml-docs-ci {"image":"ocurrent/ocaml-docs-ci@sha256:c185433c435fd21926e9fbd04cf102e7bda12c6246d9fc2c8a28981c34e50b05"}
2026-07-09 16:05.21: Checking out commit 07af7f37. To reproduce:
                       git clone --recursive "https://github.com/ocurrent/ocaml-docs-ci.git" -b "dill" && cd "ocaml-docs-ci" && git reset --hard 07af7f37
2026-07-09 16:05.21: Exec: "cp" "-a" "--" "/var/lib/ocurrent/var/git/ocaml-docs-ci.git-f2841974542651d306d2cfeb807c13b7948df1dcad0f20da42c0688683c00f56/.git" 
                           "/tmp/git-checkout2785980f"
2026-07-09 16:05.21: Exec: "git" "-C" "/tmp/git-checkout2785980f" "submodule" 
                           "deinit" "--force" "--all"
2026-07-09 16:05.21: Exec: "git" "-C" "/tmp/git-checkout2785980f" "reset" 
                           "--hard" "-q" "07af7f37465ae598d79f014e8f42a3b35cca2ce5"
2026-07-09 16:05.21: Exec: "git" "-c" "protocol.file.allow=always" "-C" 
                           "/tmp/git-checkout2785980f" "submodule" "update" 
                           "--recursive" "--init" "--no-fetch"
2026-07-09 16:05.21: docker-compose.yml
# ocaml-docs-ci, post-day11.
#
# Two services:
#   - daemon — runs the ocaml-docs-ci binary. Drives runc directly
#     to build each opam package in a sandboxed container, so it
#     needs --privileged plus access to the host's runc (the
#     debian-12 runc shipped in the image is enough on most kernels;
#     swap to a bind-mount of /usr/bin/runc if the host's is newer).
#   - web — Caddy, serving rendered HTML from the day11 cache
#     directly and reverse-proxying everything else (dashboard, job
#     logs, snapshot pages) to the daemon at :8080.
#
# The daemon resolves everything under /home/app (the image's app-user
# HOME). The whole [.day11] tree lives on a single named volume mounted
# at /home/app/.day11. The two profiles it runs are baked into the image
# at /home/app/.day11/profiles by the Dockerfile
# Everything under .day11 — cache,
# snapshots, overlays, and scratch (TMPDIR=/home/app/.day11/cache/tmp) —
# shares one volume/filesystem, so the daemon's hardlinks (cp --link
# of dep layers from cache into scratch on Stack.merge) and its cross-dir
# rename()s all stay within a single mount and just work.

services:
  daemon:
    # ocurrent-deployer pins this to a name@sha256 digest before deploying.
    image: ocurrent/ocaml-docs-ci@sha256:c185433c435fd21926e9fbd04cf102e7bda12c6246d9fc2c8a28981c34e50b05
    restart: unless-stopped
    privileged: true
    # The daemon builds (and imports) its base image via the host
    # Docker daemon — [docker build] / [docker create] over the mounted
    # socket below. The socket is group-owned by [docker]; since the
    # daemon runs as the non-root app user, add that group so it can
    # talk to the socket. This must match the docker group's gid *on the
    # host*: dill's docker.io install put it at 107 (999 was already taken
    # by systemd-journal). Update this if the host's docker gid differs.
    group_add:
      - "107"
    # The default soft nofile limit (1024) is far too low for hundreds
    # of concurrent builds — each holds open logs, overlay dirs, and
    # pipes to sudo/runc, and exhaustion (EMFILE) cascades into mass
    # build failures.
    ulimits:
      nofile:
        soft: 1048576
        hard: 1048576
    init: false   # dumb-init already in the image
    ports:
      - "8080:8080"
    command:
      # The image entrypoint is generic (it only preps TMPDIR), so the
      # command names the binary. Run one-off CLI tools in the same
      # environment with e.g.:
      #   docker compose run --rm daemon day11 batch --profile=full ...
      - ocaml-docs-ci
      # Only the lightweight 'quick' profile runs (Jane Street packages,
      # latest version only). The heavier ocaml-odoc-master and oxcaml
      # profiles are still baked into the image (docker/profiles/) but
      # disabled here — add them back to this list to re-enable.
      # 'eliom-fix' builds the latest version of every package against
      # odoc + odoc-driver from jonludlam/odoc#eliom-fix (see the
      # --github-pin-overlay below). 'eliom-baseline' is its control: an
      # identical profile (same compiler 5.4.1, same dune.3.23.1 pin, same
      # opam-repo) but WITHOUT the eliom-fix overlay, so it uses mainline
      # odoc/driver. Diffing eliom-fix vs eliom-baseline isolates the
      # fork's odoc rendering changes from compiler/solve differences.
      - --profiles=quick,full,eliom-fix,eliom-baseline,quick-ubuntu
      # Republish odoc/odoc-driver/odoc-md/odoc-parser/sherlodoc from the
      # eliom-fix branch of the jonludlam/odoc fork as an opam overlay
      # under .day11/overlays/eliom-fix/repo, which the eliom-fix profile
      # references in its opam_repositories. Tracks the branch HEAD on the
      # hourly schedule and re-plans when it moves.
      - --github-pin-overlay=https://github.com/jonludlam/odoc#eliom-fix=overlays/eliom-fix
      # All opam-repository mirrors live inside the day11 volume, under
      # .day11/repo/. The clone-target PATHs below are relative, so
      # ocaml-docs-ci resolves them against the .day11 root (the parent
      # of --profile-dir, i.e. /home/app/.day11) — exactly as it resolves
      # the matching [opam_repositories] entries in the profiles, so the
      # two line up and live repo-polling hooks up. git clone creates the
      # missing [repo/...] parents on first run.
      - --remote=https://github.com/ocaml/opam-repository.git=repo/opam-repository
      - --jobs=40
      - --cores-per-build=4
      - --overcommit=2.5
      - --port=8080
    volumes:
      # All of .day11 on a single named volume: cache (built layers +
      # rendered HTML), snapshots, overlays, the opam-repository mirrors
      # (repo/, git-cloned by --remote on first run), and scratch (TMPDIR=
      # /home/app/.day11/cache/tmp). The image-baked profiles at
      # /home/app/.day11/profiles are preserved — an empty named volume
      # is seeded from the image's directory on first creation, so unlike
      # a bind-mount it doesn't shadow them.
      - day11:/home/app/.day11
      # OCurrent state (sqlite + per-job logs). Mounted under
      # /var/lib/ocurrent matching the image's WORKDIR.
      - ocurrent-data:/var/lib/ocurrent
      # Build/import the base image via the host Docker daemon.
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      # HOME stays /home/app (set in the image); the daemon derives
      # ~/.day11/{profiles,cache} from it.
      - OCAMLRUNPARAM=b

  web:
    image: caddy:alpine
    restart: unless-stopped
    depends_on:
      - daemon
    ports:
      # 80 for the ACME HTTP-01 challenge + HTTP->HTTPS redirect, 443 for
      # TLS (and 443/udp for HTTP/3). Caddy auto-provisions a Let's Encrypt
      # cert for the domain named in the Caddyfile; both must be reachable
      # from the internet for issuance to succeed.
      - "80:80"
      - "443:443"
      - "443:443/udp"
    # The Caddyfile is shipped to the remote daemon as an inline Compose
    # config (see the top-level `configs:` block below) rather than bind-
    # mounting /etc/caddy. ocurrent-deployer runs `docker --context
    # dill.caelum.ci.dev compose up`, so any bind source would resolve on
    # the remote host's filesystem, not in the deployer's checkout — only
    # an inline config ships the file's bytes to the remote daemon.
    configs:
      - source: caddyfile
        target: /etc/caddy/Caddyfile
    volumes:
      # Serve rendered HTML straight from the shared day11 volume,
      # mounted read-only at /srv. The per-arch cache root is a subdir
      # of it (/srv/cache/debian-bookworm-x86_64); the Caddyfile prepends
      # that prefix. NB: not /data — that's Caddy's own state dir.
      - day11:/srv:ro
      # Caddy's data (instance id, any future certs) and autosaved
      # config. Named so they survive container recreation rather than
      # piling up as anonymous volumes.
      - caddy-data:/data
      - caddy-config:/config

volumes:
  day11:
  caddy-data:
  caddy-config:
  ocurrent-data:

# Caddy front-end config, inlined so the deployment is self-contained and
# needs nothing pre-placed on the host. Compose ships this content to the
# remote daemon and mounts it at /etc/caddy/Caddyfile in the web service.
configs:
  caddyfile:
    content: |
      # Caddy front-end for ocaml-docs-ci.
      #
      # Two responsibilities:
      #   1. Serve rendered HTML straight from the day11 cache.
      #      Each profile's docs are built into a per-toolchain epoch dir and
      #      exposed through a [html-live] symlink that's swapped manually on
      #      promotion. day11 lays a profile's live docs out at
      #      [<cache>/html-<profile>/html-live/p/<pkg>/<ver>/doc/index.html]
      #      (html-live -> epoch-<hash>/html), and the daemon links to
      #      [/profiles/<NAME>/docs/p/<pkg>/<ver>/...].
      #      The .day11 volume is mounted read-only at /srv (Caddy keeps its
      #      own state on /data, so we can't reuse that path). Rendered HTML
      #      is arch-independent, so it lives under a flat per-profile dir —
      #      live docs are under [/srv/cache/html/<profile>/html-live/].
      #      The html-live symlink is *relative* (epoch-<hash>/html), so it
      #      resolves within /srv. Until the first promote, html-live is absent
      #      and these routes 404 (the rest of the site still proxies fine).
      #   2. Reverse-proxy everything else to the daemon's web UI on :8080 —
      #      the dashboard, snapshot pages, /job/ log viewer, diff pages, etc.
      #
      # TLS: naming a real domain (not :80) turns on Caddy's automatic HTTPS —
      # it provisions and renews a Let's Encrypt cert over ACME and redirects
      # plain HTTP to HTTPS. Needs ports 80 + 443 reachable from the internet
      # and a DNS record for the domain pointing here. Certs/account key persist
      # on the /data volume so restarts don't re-request.
      dill.caelum.ci.dev {
        # gzip-compress the static .css/.js/.json odoc emits (and HTML).
        encode gzip

        # Allow large request bodies for anything the daemon surfaces
        # (e.g. snapshot uploads via the API).
        request_body {
          max_size 100MB
        }

        # /profiles/<name>/docs/<rest> → serve from the day11 cache.
        # Capture 1 is the profile, capture 2 the path within its html tree.
        @docs path_regexp docs ^/profiles/([^/]+)/docs/(.*)$
        handle @docs {
          root * /srv/cache/html/{re.docs.1}/html-live
          rewrite * /{re.docs.2}
          # odoc trees carry an index.html in every directory; serve it for
          # bare-directory requests. Missing files 404 (file_server default).
          file_server {
            index index.html
          }
        }

        # Everything else → the daemon. Caddy forwards the Host header and
        # sets the X-Forwarded-* headers automatically.
        handle {
          reverse_proxy daemon:8080 {
            # Don't buffer responses: the /job/ log viewer streams
            # (SSE-style) and must flush as data arrives. Caddy applies
            # no upstream read timeout by default, so long-lived streams
            # stay open (nginx needed an explicit 1d bump for this).
            flush_interval -1
          }
        }
      }

2026-07-09 16:05.21: Exec: "docker" "--context" "dill.caelum.ci.dev" 
                           "compose" "-p" "ocaml-docs-ci" "-f" "/tmp/git-checkout2785980f/docker-compose.yml" 
                           "pull"
 Image caddy:alpine Pulling 
 Image ocurrent/ocaml-docs-ci@sha256:c185433c435fd21926e9fbd04cf102e7bda12c6246d9fc2c8a28981c34e50b05 Pulling 
 a0449c657909 Pulling fs layer 0B
 e6f31ffc071e Pulling fs layer 0B
 ee31d5a470f0 Pulling fs layer 0B
 f8432a27d075 Pulling fs layer 0B
 4f4fb700ef54 Pulling fs layer 0B
 ee31d5a470f0 Downloading 7.502kB
 a0449c657909 Downloading 1.049MB
 e6f31ffc071e Downloading 1.049MB
 ee31d5a470f0 Downloading 7.502kB
 ee31d5a470f0 Download complete 0B
 a0449c657909 Downloading 1.049MB
 e6f31ffc071e Downloading 1.049MB
 e6f31ffc071e Downloading 2.097MB
 f8432a27d075 Downloading 1.049MB
 a0449c657909 Downloading 3.146MB
 f8432a27d075 Download complete 0B
 a0449c657909 Downloading 8.389MB
 e6f31ffc071e Download complete 0B
 e6f31ffc071e Extracting 1B
 a0449c657909 Downloading 10.49MB
 4f4fb700ef54 Downloading 32B
 e6f31ffc071e Extracting 1B
 4f4fb700ef54 Downloading 32B
 a0449c657909 Downloading 10.49MB
 e6f31ffc071e Extracting 1B
 a0449c657909 Downloading 15.73MB
 4f4fb700ef54 Download complete 0B
 e6f31ffc071e Extracting 1B
 a0449c657909 Download complete 0B
 e6f31ffc071e Extracting 1B
 01cedcff86f8 Pulling fs layer 0B
 5c61adb4cd02 Pulling fs layer 0B
 12c608f306e0 Pulling fs layer 0B
 a976e540c872 Pulling fs layer 0B
 6d085c30ffa6 Pulling fs layer 0B
 a513c5204ad8 Pulling fs layer 0B
 126a2d8d8f76 Pulling fs layer 0B
 2f6e1fb397d5 Pulling fs layer 0B
 8054ad6ae70f Pulling fs layer 0B
 7fccd04675d0 Download complete 0B
 a39a20466be4 Downloading 314.5kB
 e6f31ffc071e Extracting 1B
 01cedcff86f8 Downloading 1.049MB
 a39a20466be4 Downloading 314.5kB
 e6f31ffc071e Extracting 1B
 01cedcff86f8 Downloading 5.243MB
 a39a20466be4 Download complete 0B
 f8432a27d075 Extracting 1B
 e6f31ffc071e Pull complete 0B
 01cedcff86f8 Downloading 12.58MB
 ee31d5a470f0 Pull complete 0B
 f8432a27d075 Pull complete 0B
 a0449c657909 Extracting 1B
 01cedcff86f8 Downloading 18.87MB
 a0449c657909 Extracting 1B
 a513c5204ad8 Downloading 189B
 01cedcff86f8 Downloading 19.92MB
 a0449c657909 Extracting 1B
 01cedcff86f8 Downloading 22.02MB
 8054ad6ae70f Download complete 0B
 2f6e1fb397d5 Download complete 0B
 a976e540c872 Download complete 0B
 126a2d8d8f76 Download complete 0B
 6d085c30ffa6 Download complete 0B
 a513c5204ad8 Download complete 0B
 a0449c657909 Extracting 1B
 01cedcff86f8 Downloading 33.55MB
 a0449c657909 Extracting 1B
 4f4fb700ef54 Pull complete 0B
 a0449c657909 Pull complete 0B
 Image caddy:alpine Pulled 
 01cedcff86f8 Downloading 44.04MB
 01cedcff86f8 Downloading 48.5MB
 5c61adb4cd02 Downloading 1.049MB
 01cedcff86f8 Downloading 48.5MB
 5c61adb4cd02 Downloading 3.146MB
 01cedcff86f8 Download complete 0B
 5c61adb4cd02 Downloading 7.34MB
 12c608f306e0 Downloading 2.097MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 9.437MB
 12c608f306e0 Downloading 4.194MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 12.58MB
 12c608f306e0 Downloading 6.291MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 16.78MB
 12c608f306e0 Downloading 10.49MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 19.92MB
 12c608f306e0 Downloading 13.63MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 22.02MB
 12c608f306e0 Downloading 15.73MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 25.17MB
 12c608f306e0 Downloading 20.97MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 28.31MB
 12c608f306e0 Downloading 24.12MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 31.46MB
 12c608f306e0 Downloading 27.26MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 34.6MB
 12c608f306e0 Downloading 32.51MB
 01cedcff86f8 Extracting 1B
 5c61adb4cd02 Downloading 37.75MB
 12c608f306e0 Downloading 35.65MB
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 39.85MB
 12c608f306e0 Downloading 39.85MB
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 41.94MB
 12c608f306e0 Downloading 45.09MB
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 46.14MB
 12c608f306e0 Downloading 45.64MB
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 47.19MB
 12c608f306e0 Downloading 45.64MB
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 55.57MB
 12c608f306e0 Download complete 0B
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 62.91MB
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 70.25MB
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 75.5MB
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 81.79MB
 01cedcff86f8 Extracting 2B
 5c61adb4cd02 Downloading 83.89MB
 01cedcff86f8 Extracting 3B
 5c61adb4cd02 Downloading 83.89MB
 01cedcff86f8 Extracting 3B
 5c61adb4cd02 Downloading 83.89MB
 01cedcff86f8 Extracting 3B
 5c61adb4cd02 Downloading 89.13MB
 01cedcff86f8 Extracting 3B
 5c61adb4cd02 Downloading 95.42MB
 01cedcff86f8 Extracting 3B
 5c61adb4cd02 Downloading 98.57MB
 01cedcff86f8 Pull complete 0B
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 99.61MB
 5c61adb4cd02 Downloading 100.7MB
 5c61adb4cd02 Downloading 103.8MB
 5c61adb4cd02 Downloading 108MB
 5c61adb4cd02 Downloading 113.2MB
 5c61adb4cd02 Downloading 118.5MB
 5c61adb4cd02 Downloading 123.7MB
 5c61adb4cd02 Downloading 129MB
 5c61adb4cd02 Downloading 133.2MB
 5c61adb4cd02 Downloading 133.2MB
 5c61adb4cd02 Downloading 133.2MB
 5c61adb4cd02 Downloading 136.3MB
 5c61adb4cd02 Downloading 137.4MB
 5c61adb4cd02 Downloading 138.3MB
 5c61adb4cd02 Downloading 138.3MB
 5c61adb4cd02 Downloading 138.3MB
 5c61adb4cd02 Downloading 138.3MB
 5c61adb4cd02 Downloading 138.3MB
 5c61adb4cd02 Download complete 0B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 1B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 2B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 3B
 5c61adb4cd02 Extracting 4B
 5c61adb4cd02 Extracting 4B
 5c61adb4cd02 Extracting 4B
 5c61adb4cd02 Extracting 4B
 5c61adb4cd02 Extracting 4B
 5c61adb4cd02 Extracting 4B
 5c61adb4cd02 Extracting 4B
 5c61adb4cd02 Extracting 4B
 5c61adb4cd02 Extracting 4B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 5B
 5c61adb4cd02 Extracting 6B
 5c61adb4cd02 Extracting 6B
 2f6e1fb397d5 Pull complete 0B
 a513c5204ad8 Pull complete 0B
 12c608f306e0 Extracting 1B
 5c61adb4cd02 Pull complete 0B
 12c608f306e0 Extracting 1B
 12c608f306e0 Extracting 1B
 12c608f306e0 Extracting 1B
 12c608f306e0 Extracting 1B
 12c608f306e0 Extracting 1B
 12c608f306e0 Extracting 1B
 12c608f306e0 Extracting 1B
 8054ad6ae70f Pull complete 0B
 a976e540c872 Pull complete 0B
 126a2d8d8f76 Pull complete 0B
 6d085c30ffa6 Pull complete 0B
 12c608f306e0 Pull complete 0B
 Image ocurrent/ocaml-docs-ci@sha256:c185433c435fd21926e9fbd04cf102e7bda12c6246d9fc2c8a28981c34e50b05 Pulled 
2026-07-09 16:05.38: Exec: "docker" "--context" "dill.caelum.ci.dev" 
                           "compose" "-p" "ocaml-docs-ci" "-f" "/tmp/git-checkout2785980f/docker-compose.yml" 
                           "up" "-d"
 Network ocaml-docs-ci_default Creating 
 Network ocaml-docs-ci_default Created 
 Volume ocaml-docs-ci_ocurrent-data Creating 
 Volume ocaml-docs-ci_ocurrent-data Created 
 Volume ocaml-docs-ci_day11 Creating 
 Volume ocaml-docs-ci_day11 Created 
 Volume ocaml-docs-ci_caddy-data Creating 
 Volume ocaml-docs-ci_caddy-data Created 
 Volume ocaml-docs-ci_caddy-config Creating 
 Volume ocaml-docs-ci_caddy-config Created 
 Container ocaml-docs-ci-daemon-1 Creating 
 Container ocaml-docs-ci-daemon-1 Created 
 Container ocaml-docs-ci-web-1 Creating 
 Container ocaml-docs-ci-web-1 Created 
 Container ocaml-docs-ci-daemon-1 Starting 
 Container ocaml-docs-ci-daemon-1 Started 
 Container ocaml-docs-ci-web-1 Starting 
 Container ocaml-docs-ci-web-1 Started 
2026-07-09 16:08.00: Job succeeded