From 7942eb0971dc03d634dbdc13852c369d6a2f1d65 Mon Sep 17 00:00:00 2001 From: Patrick O'Reilly
Date: Mon, 6 Jul 2026 21:00:56 -0700
Subject: [PATCH] docker: publishable runtime image with in-container orlyc and
MCP-smoke gate (#530)
---
.dockerignore | 14 ++++++
.github/workflows/docker.yml | 83 +++++++++++++++++++++++++++++++++
Dockerfile | 68 +++++++++++++++++++++++++++
README.md | 19 +++++++-
changelog.d/530-docker-image.md | 1 +
docker/entrypoint.sh | 25 ++++++++++
6 files changed, 209 insertions(+), 1 deletion(-)
create mode 100644 .dockerignore
create mode 100644 .github/workflows/docker.yml
create mode 100644 Dockerfile
create mode 100644 changelog.d/530-docker-image.md
create mode 100755 docker/entrypoint.sh
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..c7e547ff
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,14 @@
+# Keep the build context to the source tree: no VCS history, no host build
+# output (the image does its own release build), no Node artifacts.
+# tools/jhm + make_dep_file are host-built (gitignored) binaries; if they
+# leak into the context, `make bootstrap` skips bootstrap.sh and the .jhm
+# tree-root marker it creates, and jhm dies at "Unable to find .jhm".
+.git
+.claude
+tools/jhm
+tools/make_dep_file
+**/node_modules
+**/dist
+**/__pycache__
+**/package-lock.json
+*.log
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 00000000..c56d5184
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,83 @@
+# Build (and on demand, publish) the Orly runtime image (#530).
+#
+# Pull requests touching the docker files build the image and gate it with
+# the MCP smoke pointed at the CONTAINERIZED server (the same smoke.mjs that
+# gates clients/mcp against a host orlyi) -- no push. A version tag or a
+# manual dispatch additionally pushes to ghcr.io/orlyatomics/orly.
+name: docker
+
+on:
+ pull_request:
+ paths:
+ - Dockerfile
+ - .dockerignore
+ - docker/**
+ - .github/workflows/docker.yml
+ push:
+ tags: ['v*']
+ workflow_dispatch:
+
+jobs:
+ image:
+ name: build image + in-container smoke
+ runs-on: ubuntu-24.04
+ timeout-minutes: 90
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Build image
+ run: docker build -t ghcr.io/orlyatomics/orly:ci .
+
+ - name: Start container
+ run: |
+ docker run -d --name orly -p 8082:8082 ghcr.io/orlyatomics/orly:ci
+ for _ in $(seq 1 60); do
+ if ss -tln | grep -q ':8082'; then exit 0; fi
+ sleep 1
+ done
+ echo "container never opened :8082; logs:"
+ docker logs orly | tail -40
+ exit 1
+
+ # The same MCP smoke that gates clients/mcp, but ORLY_URL points at the
+ # container and the `sample` package is the one BAKED into the image --
+ # exercising install/new_pov/call/call_batch/error against the published
+ # artifact end to end.
+ - name: MCP smoke against the container
+ run: |
+ (cd clients/ts && npm install --silent && npx tsc)
+ (cd clients/mcp && npm install --silent && npx tsc)
+ ORLY_URL=ws://127.0.0.1:8082/ node clients/mcp/smoke/smoke.mjs
+
+ # The image also claims in-container package compiles (orlyc + g++ +
+ # ORLY_SRC_ROOT). Gate that claim: compile a from-scratch user package
+ # to a loadable .so inside the running container. (This caught a
+ # missing uuid-dev in the runtime stage during development.)
+ - name: In-container orlyc compile
+ run: |
+ docker exec orly bash -c '
+ set -e
+ mkdir -p /tmp/user
+ printf "package #1;\nhello = (42) where {};\n" > /tmp/user/hello.orly
+ cd /tmp/user && orlyc -o /tmp/user /tmp/user/hello.orly
+ test -s /tmp/user/hello.1.so
+ '
+
+ - name: Container logs (for diagnosis)
+ if: always()
+ run: docker logs orly 2>&1 | tail -60
+
+ - name: Push to ghcr.io
+ if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
+ run: |
+ echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
+ docker tag ghcr.io/orlyatomics/orly:ci ghcr.io/orlyatomics/orly:latest
+ docker push ghcr.io/orlyatomics/orly:latest
+ if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
+ ver="${GITHUB_REF#refs/tags/}"
+ docker tag ghcr.io/orlyatomics/orly:ci "ghcr.io/orlyatomics/orly:${ver}"
+ docker push "ghcr.io/orlyatomics/orly:${ver}"
+ fi
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..80348cd4
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,68 @@
+# Orly runtime image (#530): a mem-sim `orlyi` with the example packages
+# pre-compiled, plus `orlyc` and the source headers so user `.orly` packages
+# compile INSIDE the container (orlyc shells out to g++ with -I$ORLY_SRC_ROOT).
+#
+# docker build -t ghcr.io/orlyatomics/orly .
+# docker run --rm -p 8082:8082 ghcr.io/orlyatomics/orly
+#
+# The WebSocket + JSON protocol is then on ws://127.0.0.1:8082/ -- point any
+# client driver (clients/{python,go,ts}) or the MCP server (clients/mcp) at it.
+
+# ---- build stage -----------------------------------------------------------
+
+FROM ubuntu:24.04 AS build
+
+RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
+ build-essential gcc g++ uuid-dev libgmp-dev libaio-dev libsnappy-dev \
+ libreadline-dev libboost-system-dev zlib1g-dev bison flex python3 \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /src/orly
+COPY . .
+
+# Bootstrap jhm + nycr, then build just the two production binaries this
+# image ships (jhm emits to /src/out_orly). `make version` degrades to
+# "unknown" without git -- fine for an image whose tag carries the version.
+ARG JHM_WORKER_COUNT=4
+RUN make bootstrap version \
+ && PATH="/src/orly/tools:$PATH" \
+ jhm -c release --worker-count "$JHM_WORKER_COUNT" \
+ orly/server/orlyi orly/orlyc
+
+# Pre-compile the example packages with the just-built orlyc (each orlyc run
+# stands up and tears down its embedded mem-sim server, so this also smokes
+# the binaries during the image build). `sample` is the trivial write/read
+# package the MCP smoke uses; `graph` is the agent knowledge-graph schema;
+# `market` is the prediction market.
+RUN mkdir -p /opt/orly-packages /tmp/pkgout && touch /opt/orly-packages/__orly__ \
+ && cd /tmp/pkgout \
+ && /src/out_orly/release/orly/orlyc -o /tmp/pkgout /src/orly/clients/mcp/smoke/sample.orly \
+ && /src/out_orly/release/orly/orlyc -o /tmp/pkgout /src/orly/examples/agent-swarm/graph.orly \
+ && /src/out_orly/release/orly/orlyc -o /tmp/pkgout /src/orly/examples/prediction-market/market.orly \
+ && cp /tmp/pkgout/*.so /opt/orly-packages/
+
+# ---- runtime stage ---------------------------------------------------------
+
+FROM ubuntu:24.04
+
+# Runtime .so set verified via ldd on orlyi/orlyc, plus what in-container
+# package compiles need: g++ (orlyc execs `g++ -I$ORLY_SRC_ROOT ...`) and
+# uuid-dev (generated code includes base/uuid.h ->