# Base image pinned by digest for reproducible builds. # To refresh: docker pull eceasy/cli-proxy-api:latest && docker inspect --format='{{index .RepoDigests 0}}' eceasy/cli-proxy-api:latest # Refresh cadence: quarterly OR on security advisory from upstream. # TODO: pin apk packages once apk-digest pinning workflow exists in CI (apk add nodejs npm are currently unpinned; # alpine package versions can be pinned but require per-package version discovery and maintenance). FROM eceasy/cli-proxy-api:latest@sha256:4fa722b9ff83adfbe6e350d433b7dcb754756aa63df95a7a6349767dc13b2cb7 ARG CCS_NPM_VERSION=latest # CCS integrated image: CCS CLI + CLIProxy + supervisord. # Design choice (issue #1251 final scope): single-image strategy. The originally- # proposed `:full` variant bundling claude-code/gemini-cli/grok-cli/opencode was # DROPPED on maintainer review. Rationale: smaller surface area, fewer supply- # chain dependencies, simpler tag taxonomy. Users needing AI CLIs run them in # sibling containers attached to ccs-net — the public DNS contract guarantees # http://ccs:8317 reachability. See docker/README.md#connect-your-app-to- # cliproxy. Historical record: CHANGELOG.md "### Removed" entry. RUN apk add --no-cache \ curl \ jq \ nodejs \ npm \ supervisor # Install CCS CLI. # Tradeoff: `npm install -g` does not use a lockfile — the package is already # version-pinned via CCS_NPM_VERSION (e.g. "7.80.0"), so transitive drift is # bounded to patch-level updates of CCS's own production dependencies between # publish time and image build time. This is intentional: pinning an exact # lockfile in a Docker global-install layer is fragile (lockfile format ties to # npm/Node version in the base image). The --mount=type=cache layer ensures the # resolved dependency tree is stable within a single build host's cache. # Regression test: tests/docker/image-size.sh asserts the image stays within # the size budget, which catches unexpected dep bloat. RUN --mount=type=cache,target=/root/.npm \ npm install -g @kaitranntt/ccs@${CCS_NPM_VERSION} \ && ln -sf /usr/local/lib/node_modules/@kaitranntt/ccs/dist/docker/docker-bootstrap.js /usr/local/bin/ccs-docker-bootstrap COPY supervisord.conf /etc/supervisord.conf COPY entrypoint-integrated.sh /entrypoint-integrated.sh RUN chmod +x /entrypoint-integrated.sh \ && mkdir -p /var/log/ccs EXPOSE 3000 8085 8317 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD node -e "const http=require('http');const probe=(url)=>new Promise((resolve)=>{const req=http.get(url,(res)=>{res.resume();resolve(res.statusCode<400);});req.on('error',()=>resolve(false));req.setTimeout(4500,()=>{req.destroy();resolve(false);});});Promise.all([probe('http://127.0.0.1:3000/'),probe('http://127.0.0.1:8317/')]).then((results)=>process.exit(results.every(Boolean)?0:1));" ENTRYPOINT ["/entrypoint-integrated.sh"]