Docker Quick Start
Run the CTO-GUI VM management interface on any Linux machine with KVM. No account or API keys required — just Docker and a KVM-capable host.
Prerequisites
- Linux host with KVM/libvirt installed and running
- Docker and Docker Compose (v2)
/dev/kvmaccessible (your user in thekvmgroup)- libvirt socket at
/var/run/libvirt/libvirt-sock
Verify your system is ready:
# KVM support (should return > 0)
egrep -c '(vmx|svm)' /proc/cpuinfo
# libvirt running
sudo systemctl status libvirtd
# Docker installed
docker compose version
# /dev/kvm accessible
ls -la /dev/kvmStart
Clone the repository and start the containers:
git clone https://github.com/openfactory-tech/openfactory.git
cd openfactory/cto-gui
docker compose -f docker-compose.self-hosted.yml up --build -dOpen http://localhost in your browser. No login required.
What You Get
| Feature | Available |
|---|---|
| VM creation and lifecycle (start, stop, delete) | Yes |
| Network management (create, attach, detach) | Yes |
| Network topology visualization | Yes |
| VNC console access | Yes |
| ISO upload and management | Yes |
| CIS benchmark testing | Yes |
| Automated test execution | Yes |
| Live machine monitoring | Yes |
| AI-powered OS building | No — use console.openfactory.tech |
| Security patch monitoring | No |
| Deployment scheduling | No |
| Policy document management | No |
Architecture
The Docker deployment runs two containers behind a single port:
┌──────────────────────────────────────┐
│ Browser │
│ http://localhost │
└──────────────┬───────────────────────┘
│
┌──────▼──────┐
│ Nginx │ port 80
│ (frontend) │ React SPA + reverse proxy
└──────┬──────┘
│ /api/* → backend:8001
┌──────▼──────┐
│ Backend │
│ (FastAPI) │ VM management, benchmarks,
│ │ ISO management, testing
└──────┬──────┘
│
┌──────▼──────┐
│ libvirtd │ Host's KVM/QEMU
│ (host) │ via mounted socket
└─────────────┘The frontend container serves the React app and proxies /api/* requests to the backend. The backend container connects to the host’s libvirt daemon through the mounted socket.
Managing ISOs
Upload via the UI
The sidebar shows an ISO Images panel. Click the upload icon or drag and drop .iso files directly. Uploads stream to disk — no memory issues with large files.
Pre-place on the host
Copy ISOs directly to /var/lib/libvirt/images/ on the host. They appear automatically in the UI since the container mounts this directory.
sudo cp my-custom-os.iso /var/lib/libvirt/images/Download from openfactory.tech
Build custom ISOs on console.openfactory.tech , download them, and upload to your self-hosted instance. This is the recommended workflow for teams that want AI-powered OS building with local VM management.
Creating VMs
- Select an ISO in the sidebar
- Click Create Live VM in the toolbar
- Choose the ISO, network, memory, vCPUs, and disk size
- The VM appears in the network topology and sidebar
VMs are created using the host’s libvirt — they’re real KVM virtual machines managed through the standard libvirt toolchain. You can also manage them with virsh on the host.
Configuration
Environment variables are set in docker-compose.self-hosted.yml. The defaults work for most setups.
Backend
| Variable | Default | Description |
|---|---|---|
SELF_HOSTED | true | Enables self-hosted mode (required) |
KVM_URI | qemu:///system | libvirt connection URI |
ISO_UPLOAD_DIR | /var/lib/libvirt/images | Where ISOs are stored |
MAX_ISO_UPLOAD_SIZE_GB | 10 | Maximum upload size in GB |
DEBUG | false | Set to true to enable /docs (Swagger UI) |
ALLOWED_ORIGINS | ["*"] | CORS origins — restrict in production |
Frontend
| Variable | Default | Description |
|---|---|---|
REACT_APP_SELF_HOSTED | true | Build-time flag for self-hosted UI (set during docker build) |
Volumes
| Container Path | Host Path | Purpose |
|---|---|---|
/var/run/libvirt/libvirt-sock | /var/run/libvirt/libvirt-sock | libvirt daemon socket |
/var/lib/libvirt/images | /var/lib/libvirt/images | ISO storage (shared with host) |
/app/data | Docker volume cto-data | Benchmark results, test data, logs |
Authentication
In self-hosted mode, authentication is disabled. All requests are handled as a local admin user — no login page, no tokens, no external auth providers.
This is appropriate for single-user setups and trusted LANs. For multi-user access control, see the full platform installation which includes NextAuth-based authentication.
Networking
The backend binds to port 8001 inside its container. The frontend’s Nginx proxies /api/* to backend:8001 using Docker’s internal DNS. Only port 80 is exposed to the host.
For HTTPS, place a reverse proxy (Nginx, Caddy, Traefik) in front of the frontend container:
server {
listen 443 ssl http2;
server_name vms.example.com;
ssl_certificate /etc/letsencrypt/live/vms.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vms.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:80;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 10G;
}
}Updating
Pull the latest changes and rebuild:
cd openfactory/cto-gui
git pull origin main
docker compose -f docker-compose.self-hosted.yml up --build -dData in the cto-data volume persists across rebuilds.
Troubleshooting
”FATAL: /dev/kvm does not exist”
KVM kernel module is not loaded:
sudo modprobe kvm
sudo modprobe kvm_intel # or kvm_amd“FATAL: /dev/kvm exists but is not accessible”
Add your user to the kvm group:
sudo usermod -aG kvm $USER
# Log out and back in“Could not connect to libvirt”
Ensure libvirtd is running and the socket exists:
sudo systemctl start libvirtd
ls -la /var/run/libvirt/libvirt-sockISO upload fails or times out
- Check disk space on
/var/lib/libvirt/images - For files larger than 10 GB, set
MAX_ISO_UPLOAD_SIZE_GBin the compose file - Ensure the container can write to the images directory
VMs created but not visible in UI
The backend queries libvirt directly. If VMs were created outside the UI (e.g., with virt-manager), they still appear. If they don’t, check that the KVM_URI matches your libvirt setup:
# Verify VMs are visible to libvirt
sudo virsh list --allContainer can’t reach host libvirt
The compose file mounts the libvirt socket. If your socket is at a non-standard path, update the volume mount:
volumes:
- /path/to/your/libvirt-sock:/var/run/libvirt/libvirt-sock