/ installation
Self-hosted in under 15 minutes.
13 Docker services pulled from Docker Hub — application core + observability stack. One compose file. Your data stays on your infrastructure.
System requirements
Testhide runs 13 containers — backend × 2 replicas, AI inference + training workers, MongoDB, Redis, sidecar, plus a full observability stack (Prometheus + Grafana + Tempo + Loki + Alloy). Size your server accordingly.
AI Worker is capped at 12 GB and AI API at 6 GB. On 32 GB hosts, reduce AI_ASSIST_INFER_WORKERS and skip YOLO training with AI_YOLO_MIN_ANNOTATIONS=50.
Comfortable headroom for concurrent builds, AI training, observability retention, and log streaming via Alloy/Loki.
AI API and AI Worker share an elastic memory contract via Redis — sum of their mem_limit can exceed physical RAM safely (anti-correlated workloads).
C# Agents (.NET 6)
run on your build hosts and connect to Backend via WebSocket — Windows, Linux, or macOS.
A one-shot config-init container syncs baked observability configs into named volumes on every --force-recreate deploy.
Download docker-compose.yaml
All images are pre-built on Docker Hub — no source code needed. Create a deployment directory and save this file.
View full docker-compose.yaml
expand ↓
# ==========================================
# Testhide — Production Docker Compose (13 services)
# Images: hub.docker.com/u/thuesdays
# Download the full file via the button above.
# ==========================================
name: testhide
services:
# ── Config-init (one-shot, syncs baked configs to named volumes) ─
config-init:
image: thuesdays/testhide-backend:latest
container_name: testhide-config-init
entrypoint: []
command: ["bash", "/app/scripts/sync-configs-to-volumes.sh"]
environment:
- SKIP_LLM_CHECK=true
- LOAD_AI_MODELS=false
- RUN_AI_WORKER=false
- DISABLE_CRON=true
volumes:
- grafana_dashboards:/mnt/grafana_dashboards
- grafana_provisioning:/mnt/grafana_provisioning
- loki_config:/mnt/loki_config
- prometheus_config:/mnt/prometheus_config
- tempo_config:/mnt/tempo_config
- alloy_config:/mnt/alloy_config
restart: "no"
# ── Frontend (Nginx + Angular SPA, TLS termination) ─────────────
frontend:
container_name: testhide_frontend
image: thuesdays/testhide-frontend:latest
restart: unless-stopped
ports: [ "80:80", "443:443", "7771:7771" ]
env_file: [ .env ]
environment:
- CERT_FILE=${CERT_FILE}
- CERT_KEY=${CERT_KEY}
volumes:
- ./ssl:/etc/ssl:ro
- testhide_static_data:/usr/share/nginx/html/static:ro
depends_on: [ backend, ai-api ]
networks: [ testhide-net ]
mem_limit: 256m
# ── Backend (Python API + WebSocket, 2 replicas) ─────────────────
backend:
image: thuesdays/testhide-backend:latest
restart: unless-stopped
env_file: [ .env ]
environment:
- LOAD_AI_MODELS=false
- RUN_AI_WORKER=false
- ENVIRONMENT=production
- SERVICE_NAME=testhide
- BUILD_RCA_RETRAIN_THRESHOLD=9999999 # MUST NEVER train
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- SIDECAR_URL=http://sidecar-docker:8081
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-${PUBLIC_URL}}
volumes:
- testhide_static_data:/app/static
- ./releases:/app/releases
- ./monitoring_scripts:/app/monitoring_scripts
- ./sandbox_data:/app/sandbox_data
- ./ssh_keys:/app/ssh_keys # persists across deploys
- ./docker-compose.yaml:/app/docker-compose.yaml:ro
- testhide_hf_cache:/app/hf_cache
depends_on:
mongo: { condition: service_healthy }
redis: { condition: service_healthy }
sidecar-docker: { condition: service_healthy }
networks: [ testhide-net, testhide-internal ]
mem_limit: 2.5g
deploy:
replicas: 2
resources:
limits: { cpus: "2.0", memory: 2.5g }
# ── AI API (LLM / CLIP / FAISS inference — HTTP only) ────────────
# Elastic memory: 1.5 GB reservation, 6 GB limit. Anti-correlated
# with ai-worker via Redis (ai:training:in_progress).
ai-api:
container_name: testhide_ai_api
image: thuesdays/testhide-backend:latest
env_file: [ .env ]
environment:
- LOAD_AI_MODELS=true
- RUN_AI_WORKER=false
- DISABLE_CRON=true
- HF_HOME=/app/hf_cache
- HF_HUB_OFFLINE=${HF_HUB_OFFLINE:-0}
- RC_MODEL_NAME=${RC_MODEL_NAME:-distilbert-base-uncased}
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-${PUBLIC_URL}}
volumes:
- testhide_static_data:/app/static
- testhide_hf_cache:/app/hf_cache
- ./sandbox_data:/app/sandbox_data
- ./releases:/app/releases
depends_on:
mongo: { condition: service_healthy }
redis: { condition: service_healthy }
networks: [ testhide-net ]
mem_limit: 6g
mem_reservation: 1500m
# ── AI Worker (training, vectorization, Edge AI) ─────────────────
# Elastic memory: 5 GB reservation, 12 GB limit.
ai-worker:
container_name: testhide_ai_worker
image: thuesdays/testhide-backend:latest
env_file: [ .env ]
environment:
- LOAD_AI_MODELS=true
- RUN_AI_WORKER=true
- DISABLE_CRON=true
- AI_MEMORY_LIMIT_MB=${AI_MEMORY_LIMIT_MB:-7500}
- AI_HARD_EXIT_PERCENT=${AI_HARD_EXIT_PERCENT:-88}
- AI_LLM_CTX=${AI_LLM_CTX:-8192}
- AI_MERGE_BATCH_SIZE=${AI_MERGE_BATCH_SIZE:-150}
# AI Pipeline streaming (Phase 1 + Phase 2 §3.7)
- AI_DATASET_SHARD_ROWS=${AI_DATASET_SHARD_ROWS:-50000}
- AI_COMPACT_AFTER_SHARDS=${AI_COMPACT_AFTER_SHARDS:-32}
- AI_BUDGET_SAFETY_FACTOR=${AI_BUDGET_SAFETY_FACTOR:-0.5}
- AI_TRAINING_LOCK_TTL_SEC=${AI_TRAINING_LOCK_TTL_SEC:-3600}
- AI_VECTORS_SIDECAR_ENABLED=${AI_VECTORS_SIDECAR_ENABLED:-1}
- AI_YOLO_FORCE_CPU=${AI_YOLO_FORCE_CPU:-true}
- HF_HOME=/app/hf_cache
- RC_MODEL_NAME=${RC_MODEL_NAME:-distilbert-base-uncased}
volumes:
- testhide_static_data:/app/static
- testhide_hf_cache:/app/hf_cache
- ./sandbox_data:/app/sandbox_data
- ./releases:/app/releases
depends_on:
mongo: { condition: service_healthy }
redis: { condition: service_healthy }
networks: [ testhide-net ]
mem_limit: 12g
mem_reservation: 5g
# ── MongoDB 8 ──────────────────────────────────────────────────
mongo:
container_name: testhide_mongo
image: mongo:8.2.3
restart: unless-stopped
command: ["mongod","--auth","--wiredTigerCacheSizeGB","2"]
environment:
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASS}
ports: [ "27017:27017" ]
volumes: [ "${MONGO_DATA_PATH}:/data/db" ]
networks: [ testhide-net ]
healthcheck:
test: ["CMD","mongosh","--quiet","-u","${MONGO_USER}","-p","${MONGO_PASS}","--authenticationDatabase","admin","--eval","db.adminCommand('ping')"]
interval: 30s · timeout: 5s · retries: 5
mem_limit: 3g
# ── Redis 7 ───────────────────────────────────────────────────
redis:
container_name: testhide_redis
image: redis:7-alpine
command: ["redis-server","--maxmemory","512mb","--maxmemory-policy","allkeys-lru","--appendonly","no","--requirepass","${REDIS_PASSWORD}"]
networks: [ testhide-net ]
mem_limit: 768m
# ── Redis Insight (optional admin UI, proxied via nginx) ───────
redisinsight:
container_name: testhide_redisinsight
image: redis/redisinsight:latest
depends_on:
redis: { condition: service_healthy }
networks: [ testhide-net ]
mem_limit: 256m
# ── Docker Socket Sidecar (SEC-004) ────────────────────────────
# Sidecar token auto-derived from JWT_SECRET via HMAC-SHA256.
sidecar-docker:
container_name: testhide_sidecar_docker
image: thuesdays/testhide-sidecar:latest
environment:
- JWT_SECRET=${JWT_SECRET}
- SIDECAR_ALLOWED_IMAGES=${SIDECAR_ALLOWED_IMAGES}
- SIDECAR_ALLOWED_EXEC_PATTERNS=${SIDECAR_ALLOWED_EXEC_PATTERNS}
- SIDECAR_ALLOWED_NETWORKS=${SIDECAR_ALLOWED_NETWORKS}
- SIDECAR_PORT=8081
volumes: [ "/var/run/docker.sock:/var/run/docker.sock" ]
networks: [ testhide-internal ]
mem_limit: 128m
healthcheck:
test: ["CMD","wget","-qO-","http://localhost:8081/health"]
# ── Observability: Prometheus (metrics) ────────────────────────
# Loopback bind by default. Override via PROMETHEUS_PORT_BIND.
prometheus:
image: prom/prometheus:v2.51.0
container_name: testhide-prometheus
depends_on:
config-init: { condition: service_completed_successfully }
volumes:
- prometheus_config:/etc/prometheus:ro
- prometheus_data:/prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.retention.time=30d
- --web.enable-lifecycle
- --web.enable-remote-write-receiver
ports: [ "${PROMETHEUS_PORT_BIND:-127.0.0.1:9090}:9090" ]
networks: [ testhide-net ]
mem_limit: 512m
# ── Observability: Grafana (HTTPS, dashboards) ─────────────────
grafana:
image: grafana/grafana:10.4.2
container_name: testhide-grafana
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?Set in .env}
GF_SERVER_ROOT_URL: ${GRAFANA_ROOT_URL:-${PUBLIC_URL}:3000}
GF_SERVER_PROTOCOL: https
GF_SERVER_CERT_FILE: /etc/grafana/ssl/server.crt
GF_SERVER_CERT_KEY: /etc/grafana/ssl/server.key
GF_FEATURE_TOGGLES_ENABLE: traceqlEditor
GF_AUTH_ANONYMOUS_ENABLED: "false"
volumes:
- grafana_provisioning:/etc/grafana/provisioning:ro
- grafana_dashboards:/var/lib/grafana/dashboards:ro
- grafana_data:/var/lib/grafana
- ${GRAFANA_CERT_HOST_CRT:-./ssl/testhide.crt}:/etc/grafana/ssl/server.crt:ro
- ${GRAFANA_CERT_HOST_KEY:-./ssl/testhide.key}:/etc/grafana/ssl/server.key:ro
ports: [ "3000:3000" ]
depends_on:
config-init: { condition: service_completed_successfully }
prometheus: { condition: service_started }
tempo: { condition: service_started }
loki: { condition: service_started }
networks: [ testhide-net ]
mem_limit: 256m
# ── Observability: Tempo (distributed traces, OTel) ────────────
tempo:
image: grafana/tempo:2.4.1
container_name: testhide-tempo
command: ["-config.file=/etc/tempo/tempo.yml"]
depends_on:
config-init: { condition: service_completed_successfully }
volumes:
- tempo_config:/etc/tempo:ro
- tempo_data:/tmp/tempo
ports: [ "4317:4317", "4318:4318", "3200:3200" ]
networks: [ testhide-net ]
mem_limit: 512m
# ── Observability: Loki (log storage) ──────────────────────────
loki:
image: grafana/loki:2.9.4
container_name: testhide-loki
command: -config.file=/etc/loki/loki.yml
depends_on:
config-init: { condition: service_completed_successfully }
volumes:
- loki_config:/etc/loki:ro
- loki_data:/loki
ports: [ "3100:3100" ]
networks: [ testhide-net ]
mem_limit: 256m
# ── Observability: Grafana Alloy (log/metric collector) ────────
alloy:
image: grafana/alloy:v1.5.1
container_name: testhide-alloy
volumes:
- alloy_config:/etc/alloy:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- testhide_static_data:/var/log/testhide:ro
ports: [ "12345:12345" ]
depends_on:
config-init: { condition: service_completed_successfully }
loki: { condition: service_started }
prometheus: { condition: service_started }
networks: [ testhide-net ]
mem_limit: 256m
volumes:
testhide_static_data: { name: testhide_static_data }
testhide_hf_cache: { name: testhide_hf_cache }
prometheus_data: { name: testhide_prometheus_data }
grafana_data: { name: testhide_grafana_data }
tempo_data: { name: testhide_tempo_data }
loki_data: { name: testhide_loki_data }
# Config volumes — populated by config-init from the image.
grafana_dashboards: { name: testhide_grafana_dashboards }
grafana_provisioning: { name: testhide_grafana_provisioning }
loki_config: { name: testhide_loki_config }
prometheus_config: { name: testhide_prometheus_config }
tempo_config: { name: testhide_tempo_config }
alloy_config: { name: testhide_alloy_config }
networks:
testhide-net: { name: testhide-net }
testhide-internal: { driver: bridge, internal: true }
Configure .env
Edit .env — every variable is listed below. REQUIRED must be set before first boot. OPTIONAL have working defaults.
CORS_ALLOWED_ORIGINS, GRAFANA_ROOT_URL, Angular bundles.PUBLIC_URL. Add staging/dev origins if needed. Previous wildcard default * raised a startup security warning.PRODUCTION
DEBUG
PRODUCTION=true
DEBUG=false
ENVIRONMENT=production in all prod/staging deployments — SEC-002 refuses startup with a default JWT_SECRET in those modes. Keep DEBUG=false for security headers and to disable stacktrace responses.1 so client IPs are read from X-Forwarded-For (rate limits, ban store, IP allowlists work correctly). Default 0 uses the TCP peer.INTERNAL_WS_URL
PORT
INTERNAL_WS_URL=ws://backend:8080
PORT=8080
python3 -c "import secrets; print(secrets.token_hex(32))"CERT_KEY
CERT_KEY=/etc/ssl/testhide.key
./ssl/ on the host — that directory is bind-mounted to /etc/ssl.MONGO_PASS
MONGO_PASS=your_strong_password
MONGO_PORT
MONGO_DB_NAME
MONGO_AUTH_SOURCE
MONGO_PORT=27017
MONGO_DB_NAME=testhide_database
MONGO_AUTH_SOURCE=testhide_database
REDIS_PORT
REDIS_DB
REDIS_PORT=6379
REDIS_DB=0
(2 × CPU cores) + 1. Backend runs 2 replicas — total processes = replicas × workers.GUNICORN_GRACEFUL_TIMEOUT
GUNICORN_KEEPALIVE
GUNICORN_LOG_LEVEL
GUNICORN_GRACEFUL_TIMEOUT=30
GUNICORN_KEEPALIVE=5
GUNICORN_LOG_LEVEL=info
GUNICORN_TIMEOUT if you see 502 errors during large report uploads or long AI inference calls.AI_LLM_FILE
AI_LLM_FILE=Phi-3.5-mini-instruct-Q5_K_M.gguf
Q4_K_M to save ~800 MB on 32 GB servers.AI_LLM_THREADS
AI_LLM_GPU_LAYERS
AI_LLM_THREADS=8
AI_LLM_GPU_LAYERS=0
AI_LLM_THREADS to physical CPU core count. Reduce AI_LLM_CTX to 8192 on 32 GB servers to cut peak RAM by ~4 GB. GPU_LAYERS=0 = CPU-only.ai-api and ai-worker — they share the testhide_hf_cache volume.TRANSFORMERS_OFFLINE
TRANSFORMERS_OFFLINE=0
1 after a successful first boot for air-gapped mode. Models persist in the testhide_hf_cache Docker volume between restarts.AI_HARD_EXIT_PERCENT
AI_HARD_EXIT_PERCENT=88
mem_limit=12g (12288 MB) so it fires first. Hard exit (% of container limit) triggers graceful shutdown if soft limit is missed.AI_LLM_CTX
AI_LLM_CTX=8192
8192 saves ~1.5 GB KV-cache vs 32768 — recommended on 32 GB hosts.AI_LOCK_STALE_SECS
AI_LOCK_STALE_SECS=300
AI_ASSIST_IO_WORKERS
AI_ASSIST_LLM_WORKERS
AI_ASSIST_IO_WORKERS=4
AI_ASSIST_LLM_WORKERS=1
EDGE_AI_TIMEOUT_SEC
EDGE_LLM_MODEL
EDGE_EMBEDDER_MODEL
EDGE_AI_TIMEOUT_SEC=600
EDGE_LLM_MODEL=Phi-3.5-mini-instruct-Q5_K_M.gguf
EDGE_EMBEDDER_MODEL=minilm-l6-v2.onnx
AI_LLM_FILE.AI_DATASET_MAX_ROWS
AI_COMPACT_AFTER_SHARDS
AI_DATASET_MAX_ROWS=2000000
AI_COMPACT_AFTER_SHARDS=32
AI_DATASET_MAX_ROWS. Compaction merges small shards every 32 new shards to keep file count bounded.AI_BUDGET_MIN_CHUNK_ROWS
AI_BUDGET_MAX_CHUNK_ROWS
AI_BUDGET_MIN_CHUNK_ROWS=16
AI_BUDGET_MAX_CHUNK_ROWS=100000
safety_factor=0.5 means use up to 50% of available headroom per batch. Lower on tight hosts.text_vector/image_vector from Parquet rows into raw float32 sidecar files. −60% dataset size, ×3-10 training speedup. On first deploy, migrate_vectors_to_sidecar.py runs automatically (one-shot, idempotent). Safety invariant (dataset_signature hash) blocks reads if shards diverge from sidecar. Set =0 to disable.shadow — predictions persisted to db_ai_diagnostics but not shown (safe default). active — ML label used when confidence ≥ 0.6, replacing the regex baseline. disabled — model not loaded. Auto-promotes from shadow to active when shadow cron observes ≥ 70% agreement with regex baseline over 7 days and n ≥ 50 samples.db_rca_corpus before auto-retraining fires. Backend service overrides this to 9999999 in compose — training MUST run only in ai-worker (RUN_AI_WORKER=true).LOG_LEVEL
LOG_LEVEL=INFO
trace_id/span_id so Loki + Tempo derive trace links automatically. Set LOG_LEVEL=DEBUG for verbose output (testhide loggers only — does not affect external libs)./api/v3/metrics in Prometheus exposition format. 12 baseline metrics (HTTP rps + latency, build events, WS connections, LLM cost/tokens, AI precompute duration). Scraped every 30s by Prometheus.OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_SAMPLE_RATE
SERVICE_NAME
OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4318
OTEL_SAMPLE_RATE=0.1
SERVICE_NAME=testhide
1.0 = 100% (dev), 0.1 = 10% (prod recommendation).python3 -c "import secrets; print(secrets.token_urlsafe(32))"${PUBLIC_URL}:3000 if unset.GRAFANA_CERT_HOST_KEY
GRAFANA_CERT_HOST_KEY=./ssl/testhide.key
docker-compose.yaml). By default reuses the main testhide cert. Override only if Grafana needs a separate certificate.0.0.0.0:9090 in production./api/v3/db/dump. Fail-closed — leave empty and the endpoint denies every request with HTTP 403.1 if you have a documented reason (self-signed internal webhooks, on-prem MS Teams behind a private CA). Even then, the individual webhook row must also set allow_insecure_tls=True — both gates must agree before TLS verification is skipped.AI_YOLO_FORCE_CPU
AI_YOLO_FORCE_CPU=true
yolov8n.pt is the nano model: fast, low memory. Use yolov8s.pt for better accuracy at the cost of ~2× RAM.AI_YOLO_BATCH
AI_YOLO_FREEZE
AI_YOLO_MIN_ANNOTATIONS
AI_YOLO_WEIGHT_CORRECTION
AI_YOLO_WEIGHT_MANUAL
AI_YOLO_BATCH=8
AI_YOLO_FREEZE=10
AI_YOLO_MIN_ANNOTATIONS=10
AI_YOLO_WEIGHT_CORRECTION=10
AI_YOLO_WEIGHT_MANUAL=5
AI_YOLO_MIN_ANNOTATIONS to skip training on small datasets. On 32 GB servers set to 50 to avoid OOM during training runs.LDAP_CA_BUNDLE.SIDECAR_ALLOWED_NETWORKS
SIDECAR_ALLOWED_NETWORKS=testhide-internal,bridge,testhide-net
JIRA_USERNAME
JIRA_PASSWORD
BITBUCKET_API_VERSION
JIRA_USERNAME=testhide-service
JIRA_PASSWORD=api_token
BITBUCKET_API_VERSION=1.0
LDAP_PORT
LDAP_CA_BUNDLE
LDAP_TLS_INSECURE
LDAP_PORT=636
LDAP_CA_BUNDLE=/etc/ssl/ldap-ca.pem
LDAP_TLS_INSECURE=0
./ssl/ldap-ca.pem on the host. Set LDAP_TLS_INSECURE=1 only for local dev — never in production.Place SSL certificates
Create a ./ssl/ directory next to docker-compose.yaml — it's bind-mounted read-only into Nginx.
-rw------- ssl/testhide.key # private key (chmod 600)
-rw-r--r-- ssl/ldap-ca.pem # optional: LDAP / proxy CA bundle
-keyout ssl/testhide.key -out ssl/testhide.crt \
-subj "/CN=testhide.local" && chmod 600 ssl/testhide.key
Launch the stack
First run downloads ~4–6 GB of images and AI models. Allow 10–15 minutes.
✔ Container testhide-config-init Exited (0) — configs synced
✔ Container testhide_mongo Healthy
✔ Container testhide_redis Healthy
✔ Container testhide_redisinsight Started
✔ Container testhide_sidecar_docker Healthy
✔ Container testhide_backend Started (×2 replicas)
✔ Container testhide_ai_api Started
✔ Container testhide_ai_worker Started
✔ Container testhide_frontend Started
✔ Container testhide-prometheus Started
✔ Container testhide-tempo Started
✔ Container testhide-loki Started
✔ Container testhide-grafana Started
✔ Container testhide-alloy Started
Connect build agents
A lightweight launcher wraps the agent binary, managing service lifecycle and auto-updates. Agents connect via WebSocket. Available for Windows, Linux, macOS, and Docker.
./testhide
./testhide --daemon
Auto-start on boot · runs as SYSTEM · no login required
TesthideAgent. Manage via services.msc or net stop TesthideAgent / sc.exe delete TesthideAgent.C:\ProgramData\Testhide\config.json. Verify with .\testhide.exe config --show.sudo tee /etc/apt/sources.list.d/testhide.list
Restarts automatically on crash · logs via journalctl
sudo apt-get upgrade testhide.journalctl -u testhide -f · Status: systemctl status testhide[testhide]
name=Testhide Agent
baseurl=https://dl.testhide.com/rpm
enabled=1
gpgcheck=0
EOF
sudo dnf upgrade testhide.journalctl -u testhide -f · Status: systemctl status testhide-o /opt/testhide/testhide && sudo chmod +x /opt/testhide/testhide
--url wss://YOUR_DOMAIN:7771 \
--license-key YOUR_LICENSE_KEY
[Unit]
Description=Testhide CI/CD Agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Environment=TESTHIDE_SERVICE_MODE=1
ExecStart=/opt/testhide/testhide --daemon
WorkingDirectory=/opt/testhide
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
~/.testhide/client/{version}/ from dl.testhide.com./root/.testhide/config.json when running as root.&& curl -fsSL https://dl.testhide.com/stable/testhide-macos-$ARCH \
-o testhide && chmod +x testhide
xattr -d com.apple.quarantine ./testhide or allow via System Settings → Privacy & Security.testhide-macos-x64 (Intel) · testhide-macos-arm64 (M1/M2/M3)--name testhide-agent \
--restart unless-stopped \
-e TESTHIDE_SERVICE_MODE=1 \
-e TESTHIDE_NODE_TYPE=dynamic \
thuesdays/testhide-agent:latest
--url wss://YOUR_DOMAIN:7771 \
--license-key YOUR_LICENSE_KEY
thuesdays/testhide-agent:latest · Base: Python 3.12 slim · Includes SSH server for backend remote-terminal access during test runs.-v /var/run/docker.sock:/var/run/docker.sock only if your tests need to spawn Docker containers (Docker-in-Docker).ws:// or wss://. Must match WS_URL in your server .env.uninstall-service
.\testhide.exe uninstall-service
TesthideAgent Windows Service. Requires Administrator. The launcher installs itself as a service, auto-starts on boot.TESTHIDE_SERVICE_MODE=1 internally. Used in the systemd ExecStart line — do not use for interactive sessions.dl.testhide.com. The launcher rolls back to the previous version on repeated crashes and re-downloads if needed.Verify & health-check
Run these before connecting your first pipeline.
http_requests_total{method="GET",route="/api/v3/health",status="200"} 12
...
Upgrades & operations
-u $MONGO_USER -p $MONGO_PASS \
--authenticationDatabase admin \
--out /data/db/backup_$(date +%Y%m%d)
Rather skip the infrastructure? We run it for you.
Cloud Starter ($49/mo) — managed instance, 3 concurrent builds, 30-day retention, full 8-model dashboard.