7.0 KiB
Desired Deploy Reconciler
UniDesk deployment is driven by a desired-state manifest. The manifest answers only one question: which service should run which repository commit. Runtime topology, ports, providers, compose files, Kubernetes manifests, health paths and proxy policy remain in config.json and the existing service manifests.
Manifest
The root deploy.json is intentionally minimal:
{
"schemaVersion": 1,
"services": [
{
"id": "code-queue",
"repo": "https://github.com/pikasTech/unidesk",
"commitId": "0c3cdb4ee06a23361ed511a2da033d67b53d16f4"
}
]
}
deploy.json must not contain provider IDs, ports, compose service names, Kubernetes namespace, health paths, environment variables, Dockerfile paths or build commands. The deploy reconciler joins each id with config.json.microservices[] and existing v3s manifests to resolve those details. A service listed in deploy.json but missing from config.json is an error. A service with no Dockerfile source artifact is reported as unsupported rather than silently skipped.
config.json.microservices[].repository.commitId is retained for catalog compatibility, but deploy.json is the deployment version authority for the reconciler.
CLI
bun scripts/cli.ts deploy check [--file deploy.json] [--service <id>] checks the live runtime against the desired repo and commit without changing the system.
bun scripts/cli.ts deploy plan [--file deploy.json] [--service <id>] prints the same live state plus the intended action: noop, deploy or unsupported.
bun scripts/cli.ts deploy apply [--file deploy.json] [--service <id>] [--dry-run] [--force] starts an asynchronous job. Use bun scripts/cli.ts job status <jobId> --tail-bytes 30000 to observe progress. --dry-run resolves the same plan but does not build or replace runtime objects. --force rebuilds even when the live commit matches.
All deploy commands output JSON. Long operations must use .state/jobs/ and bounded log tails; no deploy path may succeed with missing progress output.
Target-Side Build
Target-side build is the only standard deployment mode. The controller may run on the main server, but source materialization, compile/build, Docker image creation and deployment happen on the target node that will run the service.
- Main server services are fetched, built and deployed on the main server.
- D601 services are fetched, built and deployed on D601.
- D518 services are fetched, built and deployed on D518.
- v3s/k3s managed services are built on the active control target and then imported into that target's Kubernetes container runtime.
The reconciler distributes only repository URL, commit ID, Dockerfile path, build context and the existing deployment manifest/compose declaration. It must not distribute large Docker images between hosts as the default path, and it must not accept docker commit images, dirty worktrees or hand-mutated runtime containers as deployment truth.
Each target fetches the remote repository, resolves the requested commit to a full 40 character SHA and exports tracked files with git archive. Build contexts are created from that archive, not from the operator's current working tree.
One-Shot Build Proxy
Target-side Docker builds that need external network access use a one-shot build proxy scope to the main server network environment. The build path must not mutate host-global proxy settings:
- Do not edit
/etc/docker/daemon.json. - Do not edit shell profiles or global Docker CLI config.
- Do not leave long-lived host
HTTP_PROXY,HTTPS_PROXYorALL_PROXY. - Do not silently fall back to target local direct internet.
The standard implementation first uses the target Docker daemon's local BuildKit builder so target-side base image and layer caches are reused. Proxy variables are scoped to the current build process and passed as matching --build-arg values for Dockerfile RUN steps; they are not written to daemon or shell configuration. Provider targets also use docker buildx build --network host so 127.0.0.1:<proxy-port> inside RUN resolves to the target host's loopback proxy. If a service later needs an isolated docker-container builder, it may use one only as a service-specific fallback and must still log proxy resolution, proxy probe result and builder cleanup. The default path should not discard target-local image cache by creating a fresh builder for every deploy.
Provider targets should use their local provider-gateway egress proxy endpoint when available, such as http://127.0.0.1:18789. Main server targets may build without a proxy unless a service explicitly requires one.
Deployment Executors
The reconciler selects the executor from config.json:
deployment.mode=unidesk-directonmain-server: build the image on the main server, then use the fixed UniDesk Compose project andup -d --no-build --no-deps --force-recreate <service>.deployment.mode=unidesk-directon a provider: dispatchhost.sshto that provider, build on the provider, then use the service's provider-local compose file and project. The executor resolves the actual Compose project, image name, build context, Dockerfile and target from the running container labels anddocker compose config; it must not guess an image tag that the service will not actually run.deployment.mode=v3sctl-managed: dispatch to the active control target, build on that target, import the image into k3s/containerd, apply the existing Kubernetes manifest, stamp the Deployment and wait for rollout.
Existing service-specific commands such as Code Queue deploy should converge onto this reconciler path instead of keeping a parallel implementation.
Version Stamping And Verification
Every successful deployment must stamp the source version in the runtime:
- Docker image labels:
unidesk.ai/service-id,unidesk.ai/source-repo,unidesk.ai/source-commitandunidesk.ai/dockerfile. - Runtime env or Kubernetes annotations:
UNIDESK_DEPLOY_SERVICE_ID,UNIDESK_DEPLOY_REPO,UNIDESK_DEPLOY_COMMITandUNIDESK_DEPLOY_REQUESTED_COMMIT. - Service health response should expose
deploy.repoanddeploy.commitwhen practical. Existing service-specific health contracts such as Code Queue'sdeploy.commitremain valid.
The deploy job is not complete until live verification proves the running service matches the requested commit. For Docker services this includes image label inspection on the running container. For v3s/k3s services this includes Deployment annotation/env inspection and service health through the same UniDesk microservice proxy path used by the frontend. A healthy old service must fail verification.
Unsupported Services
Image-only services, such as a service declared directly as docker.io/vendor/image:tag without a Dockerfile source artifact, do not satisfy target-side build policy. They must be converted to a source repository with a Dockerfile wrapper before the reconciler can manage them. Until then, deploy check and deploy plan should report them as unsupported.