From e2af77c6fd633d7feccff6753f8c3fc89ebb6757 Mon Sep 17 00:00:00 2001
From: Patrick O'Reilly
Date: Tue, 7 Jul 2026 14:29:11 -0700
Subject: [PATCH] docker: repl entrypoint mode, orly-repl baked into the image
(#538)
`docker run -it --rm ghcr.io/orlyatomics/orly repl` now starts the
in-container mem-sim orlyi (logs to /var/log/orly/orlyi.log) and drops
straight into orly-repl pointed at it -- package dir, orlyc, and
ORLY_SRC_ROOT are all container-internal, so the REPL needs zero host
setup. Flags after `repl` pass to orly-repl; an orly-repl shim on PATH
serves `docker exec -it orly-repl` against a default-entrypoint
container.
Build stage gains node/npm and builds clients/ts + clients/repl,
pruning dev deps before they ride into the runtime stage through the
existing /src/orly -> /opt/orly/src copy; the runtime stage gains
nodejs. The entrypoint's orlyi flags move into a shared array used by
both modes.
The docker workflow's PR gate grows a repl-mode step: pipe an
expression through `docker run -i ... repl` on the freshly built image
and assert the value comes back -- covering runtime node, the baked
REPL build, and the background-orlyi handshake in one check.
---
.github/workflows/docker.yml | 12 +++++++
Dockerfile | 21 ++++++++++-
README.md | 7 ++++
changelog.d/538-docker-repl-mode.md | 1 +
clients/repl/README.md | 12 +++++--
docker/entrypoint.sh | 55 +++++++++++++++++++----------
6 files changed, 86 insertions(+), 22 deletions(-)
create mode 100644 changelog.d/538-docker-repl-mode.md
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index c56d5184..b491ed0d 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -66,6 +66,18 @@ jobs:
test -s /tmp/user/hello.1.so
'
+ # The repl entrypoint mode (#538): pipe an expression into
+ # `docker run -i ... repl` (its own in-container orlyi; no port mapping
+ # needed, and no clash with the container above) and assert the value
+ # comes back. Gates node in the runtime stage, the baked orly-repl
+ # build, and the entrypoint's background-orlyi handshake at once.
+ - name: REPL mode against the image
+ run: |
+ printf '40 + 2;\n:quit\n' \
+ | docker run -i --rm ghcr.io/orlyatomics/orly:ci repl \
+ | tee /tmp/repl-out
+ grep -qE '(^|orly> )42$' /tmp/repl-out
+
- name: Container logs (for diagnosis)
if: always()
run: docker logs orly 2>&1 | tail -60
diff --git a/Dockerfile b/Dockerfile
index 4c4144dd..0d8d3d54 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,6 +7,9 @@
#
# 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.
+#
+# Interactive orlyscript (#538): `docker run -it --rm ghcr.io/orlyatomics/orly repl`
+# starts orlyi inside the container and drops straight into orly-repl.
# ---- build stage -----------------------------------------------------------
@@ -15,6 +18,7 @@ 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 \
+ nodejs npm \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src/orly
@@ -41,6 +45,15 @@ RUN mkdir -p /opt/orly-packages /tmp/pkgout && touch /opt/orly-packages/__orly__
&& /src/out_orly/release/orly/orlyc -o /tmp/pkgout /src/orly/examples/prediction-market/market.orly \
&& cp /tmp/pkgout/*.so /opt/orly-packages/
+# Build the TS driver + orly-repl (#538); they reach the runtime stage inside
+# the /src/orly -> /opt/orly/src copy, so prune dev deps (typescript et al.)
+# down to what `node dist/index.js` needs. Order matters: the repl's npm
+# install copies clients/ts (per its `files`), so ts builds first.
+RUN cd /src/orly/clients/ts && npm install --silent && npx tsc \
+ && cd /src/orly/clients/repl && npm install --silent && npx tsc \
+ && npm prune --omit=dev \
+ && cd /src/orly/clients/ts && npm prune --omit=dev
+
# ---- runtime stage ---------------------------------------------------------
FROM ubuntu:24.04
@@ -54,9 +67,10 @@ LABEL org.opencontainers.image.source="https://github.com/orlyatomics/orly" \
# package compiles need: g++ (orlyc execs `g++ -I$ORLY_SRC_ROOT ...`) and
# uuid-dev (generated code includes base/uuid.h -> ; the lib
# alone is not enough -- verified by compiling a package in the container).
+# nodejs (no npm) runs the pre-built orly-repl.
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
g++ uuid-dev libgmp10 libgmpxx4ldbl libaio1t64 libboost-system1.83.0 \
- libreadline8t64 libsnappy1v5 zlib1g \
+ libreadline8t64 libsnappy1v5 zlib1g nodejs \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /src/out_orly/release/orly/server/orlyi /usr/local/bin/orlyi
@@ -65,6 +79,11 @@ COPY --from=build /src/out_orly/release/orly/orlyc /usr/local/bin/orlyc
COPY --from=build /src/orly /opt/orly/src
COPY --from=build /opt/orly-packages /var/lib/orly/packages
COPY docker/entrypoint.sh /usr/local/bin/orly-entrypoint
+# Shim so `docker exec -it orly-repl ...` works against a running
+# default-entrypoint container, not just the `repl` entrypoint mode.
+RUN printf '#!/bin/sh\nexec node /opt/orly/src/clients/repl/dist/index.js "$@"\n' \
+ > /usr/local/bin/orly-repl \
+ && chmod +x /usr/local/bin/orly-repl
# Trailing slash matters: the compiler concatenates paths onto this directly.
ENV ORLY_SRC_ROOT=/opt/orly/src/
diff --git a/README.md b/README.md
index 82ef87d3..8265d419 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,13 @@ image, so it compiles your own `.orly` packages too
`docker run ... ` pass straight through to `orlyi`
([#530](https://github.com/orlyatomics/orly/issues/530)).
+Or skip the client entirely and type orlyscript at it
+([#538](https://github.com/orlyatomics/orly/issues/538)):
+
+```sh
+docker run -it --rm ghcr.io/orlyatomics/orly repl
+```
+
**From source** — system dependencies (Ubuntu 24.04):
```sh
diff --git a/changelog.d/538-docker-repl-mode.md b/changelog.d/538-docker-repl-mode.md
new file mode 100644
index 00000000..05084de1
--- /dev/null
+++ b/changelog.d/538-docker-repl-mode.md
@@ -0,0 +1 @@
+- **Added**: the docker image now bakes in `orly-repl` — `docker run -it --rm ghcr.io/orlyatomics/orly repl` starts the in-container orlyi and drops straight into an orlyscript prompt (zero host setup; flags after `repl` pass to the REPL, and an `orly-repl` shim on PATH serves `docker exec` against a default-entrypoint container). PR-gated in the docker workflow by piping an expression through the published entrypoint (#538).
diff --git a/clients/repl/README.md b/clients/repl/README.md
index 19d93b8c..920a35c3 100644
--- a/clients/repl/README.md
+++ b/clients/repl/README.md
@@ -42,7 +42,15 @@ Commands at the prompt: `:help`, `:defs`, `:drop `, `:src` (show the synth
## Against the docker image
-The image ships `orlyc` but the compile/install loop needs a package directory both sides can see, so start the container with one mounted at an identical path, and run `orlyc` inside the container:
+The REPL is baked into the image ([#538](https://github.com/orlyatomics/orly/issues/538)), so the whole thing is one line — orlyi starts inside the container and the prompt appears when it's ready:
+
+```sh
+docker run -it --rm ghcr.io/orlyatomics/orly repl
+```
+
+Flags after `repl` go to orly-repl (e.g. `repl --shared`). Against an already-running default-entrypoint container, use the shim: `docker exec -it orly-repl --package-dir /var/lib/orly/packages`.
+
+If you'd rather run the REPL on the host against a containerized server (say, to join a pov your host-side agents are using), the compile/install loop needs a package directory both sides can see at the same path, and `orlyc` runs inside the container:
```sh
mkdir -p /tmp/orly-repl/packages && touch /tmp/orly-repl/packages/__orly__
@@ -53,8 +61,6 @@ npx tsc && node dist/index.js \
--orlyc "docker exec orly orlyc"
```
-(Compiled `.so` paths land in the shared mount, so the container-side `orlyc` and the host-side REPL agree on where everything is.)
-
## Building and joining agents
`orly-repl --pov ...` attaches to a POV your agents (e.g. via [`clients/mcp`](../mcp)) are already writing — the REPL then reads and writes the same live state, which makes it a debugger for agent shared memory.
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
index ef705d23..c1861660 100755
--- a/docker/entrypoint.sh
+++ b/docker/entrypoint.sh
@@ -3,23 +3,42 @@
# example packages (sample, graph, market) installable out of the box, WS on
# 8082. Extra `docker run ... ` are appended verbatim, so any orlyi
# flag can be overridden or added. Sizing knobs via env for the common case.
+#
+# `docker run -it ... repl [orly-repl flags]` (#538) instead starts that same
+# orlyi in the background (logs to /var/log/orly/orlyi.log) and drops into
+# orly-repl pointed at it; when the REPL exits, the container exits.
set -e
-exec orlyi \
- --mem_sim \
- --mem_sim_mb="${ORLY_MEM_SIM_MB:-256}" \
- --mem_sim_slow_mb="${ORLY_MEM_SIM_SLOW_MB:-64}" \
- --create=true \
- --instance_name="${ORLY_INSTANCE_NAME:-orly}" \
- --starting_state=SOLO \
- --port_number=8080 \
- --slave_port_number=8081 \
- --ws_port_number=8082 \
- --reporting_port_number=8083 \
- --connection_backlog=32 \
- --package_dir=/var/lib/orly/packages \
- --max_parallel_frames 4000 \
- --page_cache_size 256 \
- --block_cache_size 64 \
- --le --log_info \
- "$@"
+orlyi_args=(
+ --mem_sim
+ --mem_sim_mb="${ORLY_MEM_SIM_MB:-256}"
+ --mem_sim_slow_mb="${ORLY_MEM_SIM_SLOW_MB:-64}"
+ --create=true
+ --instance_name="${ORLY_INSTANCE_NAME:-orly}"
+ --starting_state=SOLO
+ --port_number=8080
+ --slave_port_number=8081
+ --ws_port_number=8082
+ --reporting_port_number=8083
+ --connection_backlog=32
+ --package_dir=/var/lib/orly/packages
+ --max_parallel_frames 4000
+ --page_cache_size 256
+ --block_cache_size 64
+ --le --log_info
+)
+
+if [ "$1" = "repl" ]; then
+ shift
+ mkdir -p /var/log/orly
+ orlyi "${orlyi_args[@]}" > /var/log/orly/orlyi.log 2>&1 &
+ for _ in $(seq 1 60); do
+ if (echo > /dev/tcp/127.0.0.1/8082) 2>/dev/null; then break; fi
+ sleep 1
+ done
+ export ORLY_URL="ws://127.0.0.1:8082/"
+ exec node /opt/orly/src/clients/repl/dist/index.js \
+ --package-dir /var/lib/orly/packages "$@"
+fi
+
+exec orlyi "${orlyi_args[@]}" "$@"