A self-hosted control panel for the Elegoo Centauri Carbon 2 3D printer. It runs as a single Docker container on your NAS or home server, talks to the printer over your LAN (no Elegoo cloud anywhere), and replicates the device page of Elegoo Slicer in any browser — installable as an app on your phone.
Features
- Live print status: progress, layers, time remaining, model thumbnail
- Full control: pause/resume/stop, jog & home axes, nozzle/bed temperatures, Model/Assistance/Case fans, chamber light, print speed modes
- File manager: printer storage & USB drive, gcode upload, start prints, delete files, print history
- Camera live view, multiplexed so any number of devices can watch at once
- Timelapse video list with download
- Multi-filament (CANVAS) hub and spool holder with load/unload/edit
- Push notifications for print events — works on iPhone even when closed
- AI print failure detection with auto-pause, running locally on the camera feed (model bundled, no cloud, no extra hardware)
- Built-in HTTPS via DuckDNS, no reverse proxy or port forwarding needed
- Installable PWA, fully responsive mobile layout
The recommended setup includes HTTPS, which unlocks push notifications and
faster PWA loads. Create a free subdomain at duckdns.org,
point it at your NAS's LAN IP (or let DUCKDNS_IP do it), and run:
services:
cc2-webui:
image: ghcr.io/nyakaspeter/elegoo-cc2-webui:latest
container_name: cc2-webui
ports:
- "443:443"
environment:
PRINTER_HOST: 192.168.1.50 # your printer's IP — set a DHCP reservation!
ACCESS_CODE: "123456" # printer Settings → Network → Access Code
DUCKDNS_DOMAIN: mycc2.duckdns.org
DUCKDNS_TOKEN: your-duckdns-token
DUCKDNS_IP: 192.168.1.10 # your NAS's LAN IP
volumes:
- ./cc2-data:/data # certificates, push subscriptions
restart: unless-stoppedOpen https://mycc2.duckdns.org — the first start takes ~30 seconds while a
Let's Encrypt certificate is obtained (DNS-01 challenge: no ports exposed to
the internet, traffic never leaves your network).
Plain HTTP instead (no DuckDNS)
Leave the DUCKDNS_* variables out and publish the HTTP port; everything
works except push notifications (which require HTTPS):
ports:
- "8080:8080"
environment:
PRINTER_HOST: 192.168.1.50
ACCESS_CODE: "123456"Any external reverse proxy with a valid certificate also works — WebSocket and camera streaming are proxied transparently.
Open the UI in Safari → Share → Add to Home Screen. The app runs fullscreen, and (over HTTPS) can receive push notifications while closed.
Everything is configured through environment variables.
| Variable | Default | Description |
|---|---|---|
PRINTER_HOST |
— | Required. Printer IP/hostname on your LAN |
ACCESS_CODE |
123456 |
LAN access code (printer Settings → Network) |
PRINTER_MQTT_PORT |
1883 |
MQTT broker port on the printer |
PRINTER_HTTP_PORT |
80 |
HTTP API port on the printer |
CAMERA_URL |
(auto) | Override the camera stream URL if detection fails |
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP port of the web UI |
DATA_DIR |
/data |
Persistent state — mount a volume here |
PUID |
1000 |
User id the app runs as / owns DATA_DIR |
PGID |
1000 |
Group id the app runs as / owns DATA_DIR |
LOG_LEVEL |
info |
Server log level (debug for protocol issues) |
MOCK_PRINTER |
0 |
Set 1 to demo the UI with a simulated printer |
The container starts as root, fixes ownership of DATA_DIR to PUID:PGID,
then drops privileges before running. On a NAS, set these to your own user so
a bind-mounted data folder is writable — on Synology/QNAP find them with id
over SSH (often 1000/100 or 1000/1000). With a named Docker volume you
can leave them at the defaults.
| Variable | Default | Description |
|---|---|---|
DUCKDNS_DOMAIN |
— | Your DuckDNS domain, e.g. mycc2.duckdns.org |
DUCKDNS_TOKEN |
— | DuckDNS account token |
DUCKDNS_IP |
— | Optional: keeps the DNS record pointed at this LAN IP |
Setting domain + token enables the bundled Caddy reverse proxy with an
automatic Let's Encrypt certificate (persisted in DATA_DIR). Without them
the container serves plain HTTP.
| Variable | Default | Description |
|---|---|---|
NOTIFY_EVENTS |
completed,failed,runout,detection |
Also: started, stopped, paused, all |
VAPID_SUBJECT |
(none) | Web Push contact, e.g. mailto:you@example.com |
NTFY_URL / NTFY_TOPIC / NTFY_TOKEN |
(none) | Optional ntfy sink |
WEBHOOK_URL |
(none) | Optional JSON webhook sink |
The server watches print-state transitions and notifies about completed, failed, filament-runout, and failure-detection events:
- Web Push — tap the bell icon in the header and allow notifications. Each device subscribes individually; works on iPhone (installed PWA, iOS 16.4+) and desktop browsers, even with the app closed. Requires HTTPS.
- ntfy — set
NTFY_URL=https://ntfy.sh(or your instance) and a secretNTFY_TOPIC, then subscribe to the topic in the ntfy app. - Webhook — events are POSTed as
{event, title, message, timestamp}.
| Variable | Default | Description |
|---|---|---|
DETECTION_ENABLED |
1 |
Set 0 to turn failure detection off |
DETECTION_NOTIFY_THRESHOLD |
0.6 |
Confidence (0–1) that triggers a notification |
DETECTION_PAUSE_THRESHOLD |
0.85 |
Confidence (0–1) that auto-pauses; 0 disables pause |
DETECTION_INTERVAL |
10 |
Seconds between analyzed camera frames |
DETECTION_CONSECUTIVE |
3 |
Above-threshold frames in a row required |
DETECTION_COOLDOWN |
600 |
Seconds between repeat notifications |
DETECTION_WARMUP |
180 |
Seconds after print start before detection begins |
DETECTION_CLASSES |
0 |
Model classes that count as failure (all = any) |
DETECTION_MODEL_URL |
(none) | Custom ONNX model URL (downloaded to DATA_DIR) |
DETECTION_MODEL_PATH |
(none) | Custom local ONNX model path (overrides URL) |
While a print is running, the server scores camera frames with a local ONNX
vision model. Detection works out of the box: the image bundles a YOLO11
print-failure model (3D-Print-Failure-Detector
by ApatheticWithoutTheA, MIT) whose classes are 0 spaghetti, 1 stringing,
2 zits — only spaghetti is monitored by default, as it's the model's most
reliable class (mAP 0.82) and the one that actually ruins prints.
With the default thresholds you get a push notification at ≥60% failure
confidence and an automatic pause at ≥85%, each requiring 3 consecutive
positive frames. Set DETECTION_PAUSE_THRESHOLD=0 if you'd rather only be
notified. To use your own model, point DETECTION_MODEL_URL or a mounted
DETECTION_MODEL_PATH at any YOLOv8/v5-style or binary-classifier ONNX
export. To dry-run the wiring without waiting for a real failure, set
DETECTION_TEST_SCORE=0.9.
The Centauri Carbon 2 exposes an MQTT broker on the printer itself (port
1883, user elegoo, password = access code). The server connects to it,
registers as a LAN client, merges the printer's delta status pushes, and
re-exposes everything as a small REST + WebSocket API consumed by the web UI.
File uploads go to the printer's HTTP API in chunks with MD5 verification.
The camera's MJPEG stream is consumed once and re-multiplexed to all viewers
and the failure detector, so the printer only ever sees one stream client.
The protocol details were derived from Elegoo's open-source elegoo-link SDK (the engine behind ElegooSlicer's device page) and community documentation, then verified against real hardware. Not affiliated with or endorsed by Elegoo.
npm install
npm run dev # mock-printer API on :8080 + Vite dev server on :5173npm run dev starts the backend in mock mode unless you export
PRINTER_HOST, so the UI can be developed without hardware. The Vite dev
server proxies /api, /ws, and /camera to the backend.
npm run build # build web + server
npm run typecheck # typecheck both workspaces
node scripts/generate-icons.mjs # regenerate PWA iconsUseful development env vars: MOCK_SPEEDUP=600 fast-forwards simulated
prints; DETECTION_TEST_SCORE=0.9 exercises detection without a model.
Pushing to main builds and publishes the multi-arch (amd64 + arm64) image
to GHCR via GitHub Actions; tagging vX.Y.Z publishes versioned tags.
MIT. The bundled failure-detection model is by ApatheticWithoutTheA (MIT) — all credit to the original author.