Deployment Guide
Deployment Guide
Section titled “Deployment Guide”Overview
Section titled “Overview”Construct can be deployed via Docker (recommended) or as a bare-metal systemd service. Both methods support the self-deploy pipeline, where the agent commits its own code changes and triggers a restart.
Key Files
Section titled “Key Files”| File | Role |
|---|---|
deploy/Dockerfile | Multi-stage Docker build |
deploy/docker-compose.yml | Compose configuration with volume mounts and env |
.dockerignore | Excludes build artifacts, secrets, and dev files |
src/tools/self/self-deploy.ts | Self-deploy tool (Docker-aware) |
src/main.ts | Application entry point |
Docker Deployment (Primary)
Section titled “Docker Deployment (Primary)”Prerequisites
Section titled “Prerequisites”- Docker and Docker Compose installed on the host
- A
~/.construct/directory for persistent data - A
~/.construct/.envfile with required environment variables
1. Configure Environment
Section titled “1. Configure Environment”Create the data directory and environment file:
mkdir -p ~/.construct/extensions/skills ~/.construct/extensions/toolsCreate ~/.construct/.env with at minimum:
OPENROUTER_API_KEY=sk-or-v1-...TELEGRAM_BOT_TOKEN=123456:ABC-DEF...See Environment Configuration for all available variables. Note that DATABASE_URL, LOG_FILE, and EXTENSIONS_DIR are pre-set in the Dockerfile to point to /data/ paths, so you do not need to set them in your .env file.
2. Build and Run
Section titled “2. Build and Run”From the project root:
docker compose -f deploy/docker-compose.yml up -d --buildThis will:
- Build a multi-stage image using
node:22-alpine - Install dependencies in a builder stage, then copy only
node_modulesto the runtime stage - Install
gitin the runtime stage (required for self-deploy commits) - Mount
~/.constructon the host to/datain the container - Load environment variables from
~/.construct/.env - Start the application with
restart: unless-stopped
3. Verify
Section titled “3. Verify”docker compose -f deploy/docker-compose.yml logs -fLook for Construct is running in the output.
Volume Layout
Section titled “Volume Layout”The host directory ~/.construct/ maps to /data inside the container:
~/.construct/ (host) --> /data/ (container) .env (env_file, not mounted inside /data) construct.db construct.db (SQLite database) construct.log construct.log (log file) extensions/ extensions/ (EXTENSIONS_DIR) SOUL.md SOUL.md IDENTITY.md IDENTITY.md USER.md USER.md skills/ skills/ tools/ tools/The .env file is read by Docker Compose via env_file: — it is injected as environment variables into the container, not mounted as a file inside /data.
Dockerfile Details
Section titled “Dockerfile Details”The Dockerfile (deploy/Dockerfile) uses a two-stage build:
Builder stage — installs dependencies:
FROM node:22-alpine AS builderWORKDIR /appCOPY package.json pnpm-lock.yaml ./RUN pnpm install --frozen-lockfileRuntime stage — copies dependencies and source:
FROM node:22-alpineWORKDIR /appRUN apk add --no-cache gitCOPY --from=builder /app/node_modules ./node_modulesCOPY package.json tsconfig.json ./COPY src/ ./src/COPY cli/ ./cli/Environment defaults baked into the image:
DATABASE_URL=/data/construct.dbLOG_FILE=/data/construct.logEXTENSIONS_DIR=/data/extensions
Entry point: node --import=tsx src/main.ts
Docker Compose Configuration
Section titled “Docker Compose Configuration”The compose file (deploy/docker-compose.yml) is minimal:
services: construct: build: context: .. dockerfile: deploy/Dockerfile volumes: - ~/.construct:/data env_file: - ~/.construct/.env restart: unless-stoppedKey points:
- Build context is
..(project root), since the compose file lives indeploy/ restart: unless-stoppedis critical for the self-deploy mechanism (see below)
Self-Deploy in Docker
Section titled “Self-Deploy in Docker”The self_deploy tool in src/tools/self/self-deploy.ts detects Docker by checking for /.dockerenv. The deploy pipeline differs between Docker and systemd:
Common Steps (Both Environments)
Section titled “Common Steps (Both Environments)”- Typecheck —
tsc --noEmitmust pass - Tests —
vitest runmust pass - Backup tag — Creates a git tag
pre-deploy-TIMESTAMPat the current HEAD - Commit — Stages
src/,cli/, andextensions/, then commits
Docker-Specific Restart
Section titled “Docker-Specific Restart”After committing, the tool calls process.exit(0) via setImmediate. The Docker restart: unless-stopped policy then restarts the container. Since the source code lives inside the container’s /app directory (not on a volume), the restarted container uses the same committed code because git tracks the working tree in-place.
Agent edits code --> self_deploy commits --> process.exit(0) --> Docker restarts container --> tsx loads updated sourceThere is no health check or auto-rollback in Docker mode. The container simply restarts. If the new code crashes on startup, Docker’s restart policy will keep retrying.
Systemd-Specific Restart
Section titled “Systemd-Specific Restart”In non-Docker environments, the tool runs sudo systemctl restart <service>, waits 5 seconds, checks systemctl is-active, and auto-rolls back with git revert HEAD if the service failed to start.
Updating the Deployment
Section titled “Updating the Deployment”To update Construct after pulling new changes:
cd /path/to/constructgit pulldocker compose -f deploy/docker-compose.yml up -d --buildThe --build flag rebuilds the image with the latest source and dependencies. The container restarts automatically.
To update without rebuilding (if only extension files changed, which live on the volume):
docker compose -f deploy/docker-compose.yml restartNon-Docker Deployment (systemd)
Section titled “Non-Docker Deployment (systemd)”For bare-metal deployment without Docker:
1. Install Dependencies
Section titled “1. Install Dependencies”git clone <repo> /opt/constructcd /opt/constructpnpm install --frozen-lockfile2. Create Environment File
Section titled “2. Create Environment File”cp .env.example .env# Edit .env with your API keys and configuration3. Create systemd Service
Section titled “3. Create systemd Service”[Unit]Description=Construct Telegram BotAfter=network.target
[Service]Type=simpleWorkingDirectory=/opt/constructExecStart=/usr/bin/node --env-file=.env --import=tsx src/main.tsRestart=on-failureUser=construct
[Install]WantedBy=multi-user.target4. Enable and Start
Section titled “4. Enable and Start”sudo systemctl enable constructsudo systemctl start constructThe self-deploy tool expects the systemd unit to be named construct by default (configurable via the serviceUnit parameter in createSelfDeployTool()). The agent process needs passwordless sudo for systemctl restart construct and systemctl is-active construct.
Rate Limiting
Section titled “Rate Limiting”Self-deploy is rate-limited to 3 deploys per hour in both Docker and systemd modes. The rate limit is tracked in-memory (deployHistory array in self-deploy.ts), so it resets on process restart.