build(docker): add core application image
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
.git
|
||||||
|
.worktrees
|
||||||
|
.superpowers
|
||||||
|
.codex
|
||||||
|
.agents
|
||||||
|
**/.DS_Store
|
||||||
|
**/.env
|
||||||
|
**/.env.*
|
||||||
|
!**/.env.example
|
||||||
|
**/.npmrc
|
||||||
|
**/.pypirc
|
||||||
|
**/.netrc
|
||||||
|
**/.ssh
|
||||||
|
**/*.key
|
||||||
|
**/*.pem
|
||||||
|
**/*.p12
|
||||||
|
**/*.pfx
|
||||||
|
**/.venv
|
||||||
|
**/node_modules
|
||||||
|
**/__pycache__
|
||||||
|
**/.pytest_cache
|
||||||
|
**/.ruff_cache
|
||||||
|
**/coverage
|
||||||
|
**/dist
|
||||||
|
backend/data
|
||||||
|
harness/workspaces
|
||||||
|
harness/sessions
|
||||||
|
harness/artifacts
|
||||||
|
harness/indexes
|
||||||
|
harness/corpus
|
||||||
|
deploy
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# Container Packaging Task 3 Report
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Implemented the multi-stage core application image, non-root runtime, pinned Pi installation,
|
||||||
|
container entrypoint, context exclusions, and an in-image health smoke test.
|
||||||
|
|
||||||
|
## TDD / Build Evidence
|
||||||
|
|
||||||
|
Initial RED:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docker build -f docker/core.Dockerfile -t thothii-core:test .
|
||||||
|
ERROR: failed to build: resolve : lstat docker: no such file or directory
|
||||||
|
```
|
||||||
|
|
||||||
|
The first sandboxed attempt could not access the Docker socket; the authorized rerun reached the
|
||||||
|
builder and failed for the expected reason: the Dockerfile did not exist.
|
||||||
|
|
||||||
|
GREEN build:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sh -n docker/core-entrypoint.sh docker/smoke/core-smoke.sh
|
||||||
|
docker build --progress=plain -f docker/core.Dockerfile -t thothii-core:test .
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: shell syntax exited 0; Docker build exited 0. A final rebuild after tightening
|
||||||
|
`.dockerignore` also exited 0 and transferred only 17.60 kB of changed context (the initial clean
|
||||||
|
build transferred 1.02 MB).
|
||||||
|
|
||||||
|
## Runtime and Entrypoints
|
||||||
|
|
||||||
|
- Runtime user is `10001:10001` (`thoth`), never root.
|
||||||
|
- Runtime contains Node `v22.19.0` and Python `3.12.13`. Python 3.12 is intentional because the
|
||||||
|
harness declares `requires-python = ">=3.12"` and also satisfies the deployment floor of 3.11+.
|
||||||
|
- Pi is installed exactly as `@earendil-works/pi-coding-agent@0.80.3`; its build-time and runtime
|
||||||
|
version probes both reported `0.80.3`.
|
||||||
|
- `server` starts `/app/backend/dist/server.js`; `doctor` routes to `tht doctor`; `preprocess`
|
||||||
|
routes to the future-facing `tht preprocess` command; explicit `tht ...` and arbitrary CLI
|
||||||
|
arguments route to the installed `tht` binary.
|
||||||
|
- The gate extension's `typebox` runtime dependency is installed from the harness lockfile.
|
||||||
|
|
||||||
|
## Smoke and Diagnostic Results
|
||||||
|
|
||||||
|
```text
|
||||||
|
docker run --rm thothii-core:test doctor
|
||||||
|
config: error - configuration is invalid or unreadable
|
||||||
|
data_root: ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: expected exit 1 for absent mounted workspace configuration, with no traceback and no
|
||||||
|
secret-bearing validation detail.
|
||||||
|
|
||||||
|
```text
|
||||||
|
docker run --rm --entrypoint /app/docker/smoke/core-smoke.sh thothii-core:test
|
||||||
|
backend listening on http://127.0.0.1:8787
|
||||||
|
v22.19.0
|
||||||
|
Python 3.12.13
|
||||||
|
core smoke: ok
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: exit 0. The script asserted non-root execution, `tht --help`, `pi --version`, runtime
|
||||||
|
version floors, and `GET /health` through curl. Fastify's returned display address was loopback;
|
||||||
|
the inspected container environment is `HOST=0.0.0.0`, and the compiled server passes that value
|
||||||
|
to `app.listen`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
docker run --rm thothii-core:test tht --version
|
||||||
|
0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: arbitrary `tht` entrypoint exited 0.
|
||||||
|
|
||||||
|
An explicit runtime assertion checked UID 10001, exact Node and Pi versions, Python 3.11+, and the
|
||||||
|
absence of `/app/harness/.env` and `/app/harness/workspaces`; it exited 0.
|
||||||
|
|
||||||
|
## Image Size and Containment Inspection
|
||||||
|
|
||||||
|
```text
|
||||||
|
docker image inspect thothii-core:test --format '{{.Size}} {{json .Config.User}} {{json .Config.Env}}'
|
||||||
|
221419008 "10001:10001" [...runtime paths and version metadata only...]
|
||||||
|
```
|
||||||
|
|
||||||
|
Image size: **221,419,008 bytes** (about 211.2 MiB).
|
||||||
|
|
||||||
|
`docker history --no-trunc thothii-core:test` was inspected. It contains only Dockerfile commands,
|
||||||
|
the pinned public package name/version, base-image metadata, and non-sensitive runtime variables;
|
||||||
|
no credentials or customer paths were found. An in-image filename scan found only
|
||||||
|
`/app/harness/.pi/settings.json` among `.env`, key/certificate, and settings-name candidates; that
|
||||||
|
tracked Pi file contains theme/startup preferences, not secrets. The build asserts `.env` and
|
||||||
|
workspace directories are absent.
|
||||||
|
|
||||||
|
`.dockerignore` excludes VCS/agent state, all environment files except examples, package-manager
|
||||||
|
credential files, SSH/private-key and certificate formats, local virtualenvs/node_modules/caches,
|
||||||
|
backend runtime data, customer workspaces, sessions, artifacts, indexes, corpus, and deployment
|
||||||
|
mount content.
|
||||||
|
|
||||||
|
## Self-review
|
||||||
|
|
||||||
|
- `git diff --check` is clean.
|
||||||
|
- Entrypoint processes use `exec`, preserving container signal handling.
|
||||||
|
- Backend production dependencies are pruned; TypeScript build tools remain in the build stage.
|
||||||
|
- The writable `/data` root is owned by UID 10001; application payload remains root-owned and
|
||||||
|
read-only to the runtime user.
|
||||||
|
- CA certificates and curl are present for HTTPS integrations and health probing.
|
||||||
|
- No existing source, customer workspace, secret, or unrelated progress-ledger change is included
|
||||||
|
in the task commit.
|
||||||
|
|
||||||
|
## Concerns
|
||||||
|
|
||||||
|
- The `tht preprocess` command is deliberately a future-facing routing contract; its CLI group is
|
||||||
|
scheduled in the Evidence/preprocessing plan and is not implemented in the current harness.
|
||||||
|
- Python dependencies are range-resolved because the existing harness has no Python lockfile. The
|
||||||
|
Pi package, Node runtime, and package-lock-backed Node dependency sets are pinned/reproducible.
|
||||||
|
- The image was built and smoked on Docker Desktop arm64. The chosen official multi-arch base
|
||||||
|
images and Pi package are architecture-neutral at the package level, but amd64 still needs a CI
|
||||||
|
build/smoke before being advertised as verified.
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
case "${1:-server}" in
|
||||||
|
server)
|
||||||
|
shift || true
|
||||||
|
exec node /app/backend/dist/server.js "$@"
|
||||||
|
;;
|
||||||
|
doctor)
|
||||||
|
shift
|
||||||
|
exec tht doctor "$@"
|
||||||
|
;;
|
||||||
|
preprocess)
|
||||||
|
shift
|
||||||
|
exec tht preprocess "$@"
|
||||||
|
;;
|
||||||
|
tht)
|
||||||
|
shift
|
||||||
|
exec tht "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exec tht "$@"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM node:22.19.0-bookworm-slim AS node-runtime
|
||||||
|
|
||||||
|
RUN npm install --global --ignore-scripts --no-audit --no-fund \
|
||||||
|
@earendil-works/pi-coding-agent@0.80.3 \
|
||||||
|
&& pi --version
|
||||||
|
|
||||||
|
FROM node:22.19.0-bookworm-slim AS backend-build
|
||||||
|
WORKDIR /src/backend
|
||||||
|
COPY backend/package.json backend/package-lock.json ./
|
||||||
|
RUN npm ci --no-audit --no-fund
|
||||||
|
COPY backend/ ./
|
||||||
|
RUN npm run build \
|
||||||
|
&& npm prune --omit=dev
|
||||||
|
|
||||||
|
FROM node:22.19.0-bookworm-slim AS gate-deps
|
||||||
|
WORKDIR /src/harness
|
||||||
|
COPY harness/package.json harness/package-lock.json ./
|
||||||
|
RUN npm ci --no-audit --no-fund
|
||||||
|
|
||||||
|
FROM python:3.12-slim-bookworm AS runtime
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install --yes --no-install-recommends ca-certificates curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& useradd --create-home --uid 10001 thoth \
|
||||||
|
&& mkdir -p /app/backend /app/docker/smoke /data/settings \
|
||||||
|
&& chown -R thoth:thoth /data
|
||||||
|
|
||||||
|
COPY --from=node-runtime /usr/local/bin/node /usr/local/bin/node
|
||||||
|
COPY --from=node-runtime /usr/local/lib/node_modules /usr/local/lib/node_modules
|
||||||
|
RUN ln -s /usr/local/lib/node_modules/@earendil-works/pi-coding-agent/dist/cli.js /usr/local/bin/pi \
|
||||||
|
&& node --version \
|
||||||
|
&& pi --version
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY harness/ /app/harness/
|
||||||
|
COPY --from=gate-deps /src/harness/node_modules /app/harness/node_modules
|
||||||
|
RUN python -m venv /opt/venv \
|
||||||
|
&& /opt/venv/bin/pip install --no-cache-dir /app/harness
|
||||||
|
|
||||||
|
COPY --from=backend-build /src/backend/dist /app/backend/dist
|
||||||
|
COPY --from=backend-build /src/backend/node_modules /app/backend/node_modules
|
||||||
|
COPY docker/core-entrypoint.sh /app/docker/core-entrypoint.sh
|
||||||
|
COPY docker/smoke/core-smoke.sh /app/docker/smoke/core-smoke.sh
|
||||||
|
RUN chmod 0555 /app/docker/core-entrypoint.sh /app/docker/smoke/core-smoke.sh \
|
||||||
|
&& test ! -e /app/harness/.env \
|
||||||
|
&& test ! -d /app/harness/workspaces
|
||||||
|
|
||||||
|
ENV PATH="/opt/venv/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
|
||||||
|
HOST=0.0.0.0 \
|
||||||
|
PORT=8787 \
|
||||||
|
THT_HARNESS_DIR=/app/harness \
|
||||||
|
THT_BIN=/opt/venv/bin/tht \
|
||||||
|
PI_BIN=/usr/local/bin/pi \
|
||||||
|
THT_DATA_ROOT=/data \
|
||||||
|
SETTINGS_FILE=/data/settings/settings.json \
|
||||||
|
HOME=/home/thoth
|
||||||
|
|
||||||
|
WORKDIR /app/harness
|
||||||
|
EXPOSE 8787
|
||||||
|
USER 10001:10001
|
||||||
|
ENTRYPOINT ["/app/docker/core-entrypoint.sh"]
|
||||||
|
CMD ["server"]
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
test "$(id -u)" != "0"
|
||||||
|
|
||||||
|
node_version="$(node --version)"
|
||||||
|
python_version="$(python --version 2>&1)"
|
||||||
|
case "$node_version" in
|
||||||
|
v22.19.*|v22.2[0-9].*|v2[3-9].*|v[3-9][0-9].*) ;;
|
||||||
|
*) echo "Node 22.19+ required, found $node_version" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$python_version" in
|
||||||
|
"Python 3.1"[1-9].*|"Python 3."[2-9][0-9].*) ;;
|
||||||
|
*) echo "Python 3.11+ required, found $python_version" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
tht --help >/dev/null
|
||||||
|
pi --version >/dev/null
|
||||||
|
|
||||||
|
/app/docker/core-entrypoint.sh server &
|
||||||
|
server_pid=$!
|
||||||
|
trap 'kill "$server_pid" 2>/dev/null || true; wait "$server_pid" 2>/dev/null || true' EXIT INT TERM
|
||||||
|
|
||||||
|
attempt=0
|
||||||
|
until curl --fail --silent --show-error http://127.0.0.1:8787/health >/dev/null; do
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
if [ "$attempt" -ge 30 ]; then
|
||||||
|
echo "backend health check did not become ready" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$node_version"
|
||||||
|
echo "$python_version"
|
||||||
|
echo "core smoke: ok"
|
||||||
Reference in New Issue
Block a user