- New agentlib module (gitutil + deployer with NewGo / NewPHP) replaces agent-micro/internal so both agents can share it (Go's internal/ rule was blocking agent-gateway from importing agent-micro's packages). - Migrate agents from legacy github.com/docker/docker/client to the current github.com/moby/moby/client v0.5.0 / moby/moby/api v1.55.0. - Fix compile errors in the original committed code: missing gorilla/websocket import in control-plane/internal/ws/handlers.go, unaliased dockerclient reference, wrong SQLite driver name (sqlite3 -> sqlite), Dialer.Dial 3-return-value mismatch. - scripts/build.sh: Go 1.23 -> 1.24, apk add git, safe.directory for bind-mounted host tree, chmod inside container (host can't chmod files owned by container root). - README and REQUIREMENTS updated to reflect the actual architecture (Go + SQLite, no Spring Boot, moby SDK, per-deploy no image build) with a per-feature status checklist at the end of REQUIREMENTS.
6.5 KiB
Sandbox Deployment Platform (SDP)
Internal deployment platform for Backend/QA. Lets a developer deploy a feature branch into an isolated sandbox, with the API Gateway routing selected services to the sandbox and the rest to OCP. See REQUIREMENTS.md for the full spec.
Status (Slice 1 — build green, MVP core flow)
./scripts/build.sh produces three Linux/amd64 binaries and a static
dashboard. The MVP core flow — login, deploy a microservice or the PHP
gateway, watch progress and logs in real time — works end to end. The
sandbox / template / route management described in REQUIREMENTS.md is
deferred to Slice 2 and is not yet built. See
REQUIREMENTS.md for a per-feature checklist.
Layout
.
├── protocol/ # shared wire types (Event, DeployRequest)
├── agentlib/ # Go. Shared agent library: gitutil + deployer (Go/PHP flavours)
├── control-plane/ # Go. HTTP API + WS hub + SQLite/.log persistence
├── agent-micro/ # Go. Runs on 172.18.136.92, deploys Go microservices
├── agent-gateway/ # Go. Runs on 172.18.139.186, deploys the PHP API Gateway
├── dashboard/ # NextJS static export, served by nginx
├── nginx/ # reverse proxy + try_files for the dashboard
├── scripts/ # build, deploy, ssh wrappers, nginx patch
├── docker-compose.yml # all three services on alpine:latest
├── go.work # Go workspace — one build, five modules
└── bin/ # build output (gitignored)
agentlib/ is a shared library used by both agents. It owns the git
helpers and the per-deployment state machine, which has two constructors
for two build flavours:
NewGo— for microservices. Runsgo buildon the host, thendocker run alpine:3.20with the host repo bind-mounted at/srcand the binary as the container command.alpine:3.20must be pre-loaded on the host (see Offline VMs).NewPHP— for the API Gateway. Runscomposer install --no-devon the host as a best-effort step (skipped ifcomposerorcomposer.jsonare absent), thendocker run php:8.3-apachewith the repo bind-mounted at/app.php:8.3-apachemust be pre-loaded on the host. The agent is written in Go; the thing it deploys is a PHP project.
Prerequisites
- Docker (for the build container)
- Node 18+ (for the dashboard)
sshpass(for the deploy scripts:brew install sshpass)
No Go install needed locally — scripts/build.sh cross-compiles inside
golang:1.24-alpine.
Build
./scripts/build.sh
Outputs:
bin/control-plane,bin/agent-micro,bin/agent-gateway(Linux/amd64 ELF, statically linked)dashboard/out/(NextJS static export)
The build script:
- Starts a
golang:1.24-alpinecontainer with the repo bind-mounted. apk add git(the base image has none).- Configures
safe.directory /srcso the container's root user can read the bind-mounted host tree. - Cross-compiles all three binaries with
GOOS=linux GOARCH=amd64 CGO_ENABLED=0,-trimpath(reproducible builds) and-ldflags="-s -w"(strip debug info). chmod +xthe binaries inside the container (the host user can't chmod files written by the container's root).- Builds the Next.js dashboard with
npm install && npm run build.
The script verifies each binary with file to catch a missing
GOOS/GOARCH.
Deploy
./scripts/deploy.sh
This script:
- SSHs to 172.18.136.92 (
administrator) and pushesbin/agent-microto~/SDP/bin/ - SSHs to 172.18.139.186 (
administrator) and pushesbin/control-plane,bin/agent-gateway, anddashboard/out/to~/SDP/ - Idempotently splices the SDP location block into
/etc/nginx/sites-available/defaulton 186 and reloads nginx
Override the creds via SDP_92_PASS / SDP_186_PASS env vars.
Local dev (docker compose)
For dev on a single host (e.g. a laptop with Docker):
./scripts/build.sh
docker compose up -d
Three services come up on alpine:latest:
control-plane→:8080agent-micro(connects to control plane, has docker socket + repos mounted)agent-gateway(same shape)
Architecture notes
- Pass-through creds. Bitbucket credentials travel with each deploy
request from control plane to agent, are used once for
git fetch/checkout/pull, and are never logged or persisted on the agent. - No Dockerfile build on the agent. Each agent does the language
build on the host (Go or composer), then
docker run <base-image>with the host repo bind-mounted and the binary / apache as the container command. The base image must be pre-loaded. - Offline VMs.
alpine:3.20andphp:8.3-apacheare pre-loaded viadocker load. The dashboard is a static export, no runtime fetches. - Persistence. Deployment progress goes to SQLite
(
<data>/sdp.db). Log lines go to append-only<data>/logs/<deploymentId>.log. SQLite usesmodernc.org/sqlite(pure Go, no cgo) so the control plane binary stays statically linkable. The driver name issqlite(notsqlite3). - Docker SDK. The agents use the official Moby Go SDK at
github.com/moby/moby/clientv0.5.0. - Realtime transport. WebSocket end-to-end. Agents connect to
/ws/agenton the control plane; the dashboard subscribes to/ws/deployments/{id}.
MVP stubs (intentional, not Slice 1 scope)
These are marked with ponytail: comments in the code and are
scheduled for later slices. They are not in scope for Slice 1.
validateViaAgent(login) — accepts any creds if an agent is connected. Real impl: agit ls-remoteprobe frame to the agent (control-plane/internal/api/api.go:126).handleListRepos/handleListBranches— hardcoded fixtures. Real impl: alist_repos/list_branchesframe to the connected agent. Thegitutil.ListBrancheshelper and theagentlibframe protocol are not yet wired up.handleListDeployments(GET) — returns[]. Real impl reads SQLite (control-plane/internal/api/api.go:182).- WS auth on
/ws/deployments/*— open. Real impl checks session token. - Sandbox, Template, Route, Environment CRUD — entirely deferred to Slice 2. The data model, REST endpoints, and dashboard pages do not exist yet.
See also
- REQUIREMENTS.md — full spec, infra, MVP success criteria, per-feature status checklist
- nginx/nginx.conf — reference nginx config
- docker-compose.yml — three-service dev stack