Standalone Express service that runs on a VPS to build custom HuggingFace LLM Docker images on demand.
moltghost (Next.js) ──────► moltghost-builder (VPS) ──────► Docker Hub
│
└──► callback ──────► moltghost (Next.js)
- Receives build requests from
moltghost - Runs
docker buildwith a HuggingFace model baked in via Ollama - Pushes the final image to Docker Hub
- Sends a callback to
moltghostwhen done (success or failure)
Builds run serially (one at a time) — new requests are queued in memory (FIFO).
moltghost-builder/
├── src/
│ ├── index.ts ← Entry point, Express server (port 3001)
│ ├── routes/
│ │ └── build.ts ← POST /build — receive build request
│ ├── jobs/
│ │ └── buildJob.ts ← docker build + push + callback logic
│ └── queue.ts ← Serial job queue (one build at a time)
├── dockerfiles/
│ └── Dockerfile.hf ← Dockerfile template for HuggingFace models
├── .env ← Secrets (never commit)
├── .env.example ← Template for .env
├── package.json
├── tsconfig.json
└── README.md
Trigger a Docker image build for a custom HuggingFace model.
Headers:
Authorization: Bearer <BUILDER_SECRET>
Content-Type: application/json
Body:
{
"modelId": "cm_abc123",
"hfRepo": "mistralai/Mistral-7B-v0.1",
"hfToken": "hf_xxxxxxxxxxxx",
"imageTag": "moltghost/moltghost-agent:custom-cm_abc123",
"callbackUrl": "https://moltghost.io/api/custom-model/cm_abc123/callback",
"callbackSecret": "xxxxxxxx"
}Response (202 Accepted):
{
"queued": true,
"modelId": "cm_abc123",
"position": 1
}Build runs asynchronously — response returns immediately, callback fires when done.
{ "ok": true, "queue": 0, "building": false }When a build finishes, moltghost-builder POSTs to callbackUrl:
Headers: X-Callback-Secret: <callbackSecret>
Success:
{ "modelId": "cm_abc123", "status": "ready", "dockerImage": "moltghost/moltghost-agent:custom-cm_abc123" }Failure:
{ "modelId": "cm_abc123", "status": "failed", "error": "ollama pull failed: model not found" }| Variable | Description |
|---|---|
PORT |
Port to listen on (default 3001) |
BUILDER_SECRET |
Shared secret — must match moltghost env var |
DOCKERHUB_USERNAME |
Docker Hub username for docker login |
DOCKERHUB_TOKEN |
Docker Hub access token |
IMAGE_PREFIX |
Image name prefix (e.g. moltghost/moltghost-agent) |
Copy .env.example → .env and fill in values before starting.
# 1. Install Docker
apt update && apt install -y docker.io
systemctl enable --now docker
# 2. Login to Docker Hub
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
# 3. Clone builder project
git clone https://github.com/moltghost/moltghost-builder.git /opt/moltghost-builder
cd /opt/moltghost-builder
# 4. Install deps + build
npm install
npm run build
# 5. Configure
cp .env.example .env
nano .env # fill in secrets
# 6. Run with PM2
npm install -g pm2
pm2 start dist/index.js --name moltghost-builder
pm2 save
pm2 startup| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 8 GB | 16 GB |
| Disk | 60 GB | 120 GB |
| CPU | 2 core | 4 core |
| Network | 50 Mbps | 100 Mbps+ |
| OS | Ubuntu 22.04 | Ubuntu 22.04 |
GPU is not required on the VPS — Ollama only downloads model weights during build, not doing inference.
- Never expose port 3001 to the public internet. Use an internal VPS network or Nginx reverse proxy with IP allowlist.
BUILDER_SECRETmust be a strong random string (32+ chars).- HF tokens are passed in-memory during the build process only and never written to disk or DB.
- Callbacks are authenticated via
X-Callback-Secret. - All input fields are validated against strict regex patterns before being passed to
docker build.
ollama pull hf.co/...supports GGUF format models only. The model owner must publish a GGUF version to HuggingFace first.- If the builder restarts mid-build, the in-progress build is lost. Re-trigger the build from
moltghost. Future improvement: persist queue to Redis or SQLite.