graphwiz.ai
← Back to XR

Self-Hosted XR Development Environment with Docker & Traefik

XRDockerTraefikWebXRDevOpsThree.jsSelf-Hosting

Self-Hosted XR Development Environment with Docker & Traefik

Creating a professional XR (Extended Reality) development environment can be complex, but containerization with Docker and reverse proxying with Traefik makes it manageable and scalable. This comprehensive guide walks you through setting up a complete, production-ready XR development stack that you can self-host with full control.

Introduction

Why Self-Host XR Development?

Modern XR development requires multiple services working together: development servers, build tools, backend APIs, real-time communication servers, and often database systems. Self-hosting gives you:

  • Complete control over your development environment
  • No vendor lock-in - your tools, your way
  • Privacy and security - your code never leaves your infrastructure
  • Cost efficiency - no subscription fees for development tools
  • Customization - tailor every component to your specific needs

Benefits of Containerized XR Workflows:

  • Reproducibility - identical environments across team members
  • Isolation - separate projects don't interfere with each other
  • Scalability - easily add more resources when needed
  • Easy deployment - move from development to production with confidence
  • Team collaboration - share consistent development environments

Prerequisites

Hardware Requirements

Minimum Requirements:

  • CPU: 4 cores (Intel i5 or equivalent)
  • RAM: 8GB (16GB recommended)
  • Storage: 50GB SSD
  • GPU: Integrated graphics acceptable for basic 3D work

Recommended Requirements:

  • CPU: 8+ cores (Intel i7/i9, AMD Ryzen 7/9)
  • RAM: 32GB+
  • Storage: 500GB+ NVMe SSD
  • GPU: NVIDIA RTX 3060 or equivalent with 6GB+ VRAM
  • Network: Gigabit Ethernet (for team collaboration)

Software Dependencies

Required Software:

  • Docker Engine 24.0+
  • Docker Compose v2.20+
  • NVIDIA Container Toolkit (for GPU acceleration)
  • Git
  • Node.js 18+ (for some XR frameworks)
  • Modern web browser with WebXR support

Network Setup Requirements:

  • Static IP or dynamic DNS service
  • Domain name (optional but recommended for SSL)
  • Open ports: 80, 443, additional ports for development tools
  • Firewall configuration for Traefik

Infrastructure Setup

Base Docker Configuration

Enable GPU Support (NVIDIA):

First, install the NVIDIA Container Toolkit:

# Add NVIDIA repository and install toolkit
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
  sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

# Configure Docker to use NVIDIA runtime
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

# Verify GPU access
docker run --rm --gpus all nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi

Optimize Docker Daemon:

Create or edit /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Hard": 64000,
      "Soft": 64000
    }
  },
  "runtimes": {
    "nvidia": {
      "path": "nvidia-container-runtime",
      "runtimeArgs": []
    }
  },
  "default-runtime": "nvidia"
}

Restart Docker:

sudo systemctl restart docker

Traefik Reverse Proxy Configuration

Create Traefik Directory Structure:

mkdir -p ~/xr-dev-env/{traefik,projects,shared}
cd ~/xr-dev-env/traefik

Create Traefik Configuration:

traefik.yml:

api:
  dashboard: true
  insecure: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

Create Docker Compose for Traefik:

docker-compose.yml:

version: '3.8'

services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    restart: unless-stopped
    command:
      - "--configFile=/etc/traefik/traefik.yml"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml
      - ./letsencrypt:/letsencrypt
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`traefik.your-domain.com`)"
      - "traefik.http.routers.api.tls=true"
      - "traefik.http.routers.api.tls.certresolver=letsencrypt"
      - "traefik.http.routers.api.service=api@internal"
      - "traefik.http.routers.api.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_USERS}"
    environment:
      - TRAEFIK_USERS=admin:$$apr1$$hashhere  # Generate with: htpasswd -nb admin password
    networks:
      - proxy

networks:
  proxy:
    external: true

Create the external network:

docker network create proxy

Start Traefik:

cd ~/xr-dev-env/traefik
docker-compose up -d

Traefik Middlewares for XR:

Create dynamic-config.yml:

http:
  middlewares:
    cors:
      headers:
        accesscontrolallowmethods: GET,POST,OPTIONS,PUT,DELETE
        accesscontrolallowheaders: Authorization,Content-Type,Depth,User-Agent,X-File-Size,X-Requested-With,X-Requested-By,If-Modified-Since,X-File-Name,Cache-Control
        accesscontrolalloworiginlist: "*"
        accesscontrolmaxage: 100
        addvaryheader: true

    websocket-headers:
      headers:
        customrequestheaders:
          X-Forwarded-Proto: "https"
        accesscontrolallowheaders: Authorization,Content-Type,Depth,User-Agent,X-File-Size,X-Requested-With,X-Requested-By,If-Modified-Since,X-File-Name,Cache-Control,Upgrade,Connection,Sec-WebSocket-Key,Sec-WebSocket-Version,Sec-WebSocket-Extensions,Sec-WebSocket-Accept,Sec-WebSocket-Protocol
        accesscontrolallowmethods: GET,POST,OPTIONS
        accesscontrolalloworiginlist: "*"

    compress:
      compress: true

    ratelimit:
      rateLimit:
        average: 100
        burst: 50

XR Development Stack Components

Three.js Development Server

Create Development Server Container:

Create directory ~/xr-dev-env/projects/threejs-dev/:

docker-compose.yml:

version: '3.8'

services:
  dev-server:
    image: node:18-alpine
    container_name: threejs-dev-server
    restart: unless-stopped
    working_dir: /app
    command: sh -c "npm install && npm run dev"
    volumes:
      - ./src:/app/src
      - ./public:/app/public
      - /app/node_modules
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.threejs-dev.rule=Host(`dev.your-domain.com`)"
      - "traefik.http.routers.threejs-dev.tls=true"
      - "traefik.http.routers.threejs-dev.tls.certresolver=letsencrypt"
      - "traefik.http.routers.threejs-dev.middlewares=websocket-headers,cors"
      - "traefik.http.services.threejs-dev.loadbalancer.server.port=3000"
    environment:
      - NODE_ENV=development
    networks:
      - proxy

networks:
  proxy:
    external: true

package.json:

{
  "name": "threejs-xr-dev",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite --host 0.0.0.0 --port 3000",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "three": "^0.160.0",
    "@types/three": "^0.160.0"
  },
  "devDependencies": {
    "vite": "^5.0.0"
  }
}

Simple Three.js + WebXR Example:

src/main.js:

import * as THREE from 'three';

let camera, scene, renderer;
let controller;

init();
animate();

function init() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x808080);

  camera = new THREE.PerspectiveCamera(
    70,
    window.innerWidth / window.innerHeight,
    0.01,
    20
  );

  // Create a simple cube
  const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
  const material = new THREE.MeshNormalMaterial();
  const cube = new THREE.Mesh(geometry, material);
  cube.position.set(0, 1.6, -1);
  scene.add(cube);

  // Lighting
  const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1);
  light.position.set(0.5, 1, 0.25);
  scene.add(light);

  // Renderer with WebXR support
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.xr.enabled = true;
  document.body.appendChild(renderer.domElement);

  // XR Controller
  controller = renderer.xr.getController(0);
  scene.add(controller);

  // Handle resize
  window.addEventListener('resize', onWindowResize);
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
  renderer.setAnimationLoop(render);
}

function render() {
  renderer.render(scene, camera);
}

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Three.js XR Development</title>
  <style>
    body { margin: 0; overflow: hidden; }
    #info {
      position: absolute;
      top: 10px;
      left: 10px;
      color: white;
      font-family: Arial, sans-serif;
    }
  </style>
</head>
<body>
  <div id="info">Three.js + WebXR Development Environment</div>
  <script type="module" src="/src/main.js"></script>
</body>
</html>

WebXR Backend Services

Real-Time Multiplayer Server (WebSocket):

Create directory ~/xr-dev-env/projects/webxr-backend/:

docker-compose.yml:

version: '3.8'

services:
  websocket-server:
    image: node:18-alpine
    container_name: webxr-websocket
    restart: unless-stopped
    working_dir: /app
    command: sh -c "npm install && npm start"
    volumes:
      - ./src:/app/src
      - /app/node_modules
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.ws-server.rule=Host(`ws.your-domain.com`)"
      - "traefik.http.routers.ws-server.tls=true"
      - "traefik.http.routers.ws-server.tls.certresolver=letsencrypt"
      - "traefik.http.routers.ws-server.middlewares=websocket-headers"
      - "traefik.http.services.ws-server.loadbalancer.server.port=8080"
    networks:
      - proxy

  redis:
    image: redis:7-alpine
    container_name: webxr-redis
    restart: unless-stopped
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    networks:
      - proxy

volumes:
  redis-data:

networks:
  proxy:
    external: true

package.json:

{
  "name": "webxr-backend",
  "version": "1.0.0",
  "scripts": {
    "start": "node src/server.js"
  },
  "dependencies": {
    "ws": "^8.16.0",
    "redis": "^4.6.0",
    "express": "^4.18.0"
  }
}

src/server.js:

const WebSocket = require('ws');
const Redis = require('redis');

// Redis client for session management
const redisClient = Redis.createClient({
  url: 'redis://redis:6379'
});

redisClient.connect().catch(console.error);

// WebSocket server
const wss = new WebSocket.Server({ port: 8080 });

const sessions = new Map();

wss.on('connection', (ws) => {
  const sessionId = generateSessionId();
  sessions.set(sessionId, ws);

  console.log(`New client connected: ${sessionId}`);

  ws.on('message', async (message) => {
    try {
      const data = JSON.parse(message);

      switch (data.type) {
        case 'join':
          await handleJoin(ws, data);
          break;
        case 'position':
          await handlePositionUpdate(sessionId, data);
          break;
        case 'chat':
          await handleChat(sessionId, data);
          break;
        default:
          console.log('Unknown message type:', data.type);
      }
    } catch (error) {
      console.error('Error handling message:', error);
    }
  });

  ws.on('close', () => {
    sessions.delete(sessionId);
    broadcastUserLeft(sessionId);
    console.log(`Client disconnected: ${sessionId}`);
  });
});

function generateSessionId() {
  return 'session_' + Math.random().toString(36).substr(2, 9);
}

async function handleJoin(ws, data) {
  const userId = data.userId || 'user_' + Math.random().toString(36).substr(2, 9);

  await redisClient.hSet('users', userId, JSON.stringify({
    userId,
    position: { x: 0, y: 0, z: 0 },
    rotation: { x: 0, y: 0, z: 0 },
    joinedAt: Date.now()
  }));

  ws.send(JSON.stringify({
    type: 'joined',
    userId,
    sessionId: generateSessionId()
  }));

  broadcastUserList();
}

async function handlePositionUpdate(sessionId, data) {
  const userId = data.userId;
  const position = data.position;
  const rotation = data.rotation;

  await redisClient.hSet('users', userId, JSON.stringify({
    userId,
    position,
    rotation
  }));

  broadcast({
    type: 'positionUpdate',
    userId,
    position,
    rotation
  }, sessionId);
}

async function handleChat(sessionId, data) {
  const message = {
    type: 'chat',
    userId: data.userId,
    text: data.text,
    timestamp: Date.now()
  };

  broadcast(message);
}

function broadcast(message, excludeSessionId = null) {
  const data = JSON.stringify(message);

  sessions.forEach((ws, sessionId) => {
    if (sessionId !== excludeSessionId && ws.readyState === WebSocket.OPEN) {
      ws.send(data);
    }
  });
}

function broadcastUserLeft(userId) {
  broadcast({
    type: 'userLeft',
    userId
  });
}

async function broadcastUserList() {
  const users = await redisClient.hGetAll('users');
  const userList = Object.values(users).map(JSON.parse);

  broadcast({
    type: 'userList',
    users: userList
  });
}

console.log('WebSocket server running on port 8080');

Development Tools

VS Code Remote Containers Setup:

Create .devcontainer/devcontainer.json:

{
  "name": "XR Development Environment",
  "image": "mcr.microsoft.com/devcontainers/javascript-node:18",
  "features": {
    "ghcr.io/devcontainers/features/node:1": {}
  },
  "customizations": {
    "vscode": {
      "extensions": [
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "formulahendry.auto-rename-tag",
        "eamodio.gitlens",
        "ms-vscode.vscode-typescript-next"
      ],
      "settings": {
        "terminal.integrated.defaultProfile.linux": "bash",
        "editor.formatOnSave": true
      }
    }
  },
  "mounts": [
    "source=${localWorkspaceFolder},target=/workspace,type=bind",
    "source=vscode-extensions,target=/root/.vscode-server/extensions,type=volume"
  ],
  "postCreateCommand": "npm install"
}

Performance Monitoring Dashboard (Grafana + Prometheus):

Create directory ~/xr-dev-env/monitoring/:

docker-compose.yml:

version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: xr-prometheus
    restart: unless-stopped
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.prometheus.rule=Host(`prometheus.your-domain.com`)"
      - "traefik.http.routers.prometheus.tls=true"
      - "traefik.http.routers.prometheus.tls.certresolver=letsencrypt"
      - "traefik.http.routers.prometheus.middlewares=auth"
      - "traefik.http.services.prometheus.loadbalancer.server.port=9090"
    networks:
      - proxy

  grafana:
    image: grafana/grafana:latest
    container_name: xr-grafana
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.grafana.rule=Host(`grafana.your-domain.com`)"
      - "traefik.http.routers.grafana.tls=true"
      - "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
      - "traefik.http.routers.grafana.middlewares=auth"
      - "traefik.http.services.grafana.loadbalancer.server.port=3000"
    depends_on:
      - prometheus
    networks:
      - proxy

volumes:
  prometheus-data:
  grafana-data:

networks:
  proxy:
    external: true

prometheus.yml:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'traefik'
    static_configs:
      - targets: ['traefik:8080']

  - job_name: 'docker'
    static_configs:
      - targets: ['172.17.0.1:9323']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

Project Templates

Basic WebXR Project

Complete Project Structure:

~/xr-dev-env/projects/basic-webxr/
├── docker-compose.yml
├── package.json
├── index.html
└── src/
    ├── main.js
    ├── xr.js
    └── utils.js

docker-compose.yml:

version: '3.8'

services:
  basic-webxr:
    image: node:18-alpine
    container_name: basic-webxr
    restart: unless-stopped
    working_dir: /app
    command: sh -c "npm install && npm run dev"
    volumes:
      - .:/app
      - /app/node_modules
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.basic-webxr.rule=Host(`basic-webxr.your-domain.com`)"
      - "traefik.http.routers.basic-webxr.tls=true"
      - "traefik.http.routers.basic-webxr.tls.certresolver=letsencrypt"
      - "traefik.http.routers.basic-webxr.middlewares=websocket-headers,cors"
      - "traefik.http.services.basic-webxr.loadbalancer.server.port=5173"
    networks:
      - proxy

networks:
  proxy:
    external: true

Performance Optimization

GPU Passthrough Configuration

Configure Docker Compose for GPU Access:

services:
  xr-app:
    image: your-xr-app
    runtime: nvidia
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu, utility, compute]

Network Latency Optimization

Optimize Docker Network:

Create custom network with MTU optimization:

docker network create \
  --driver bridge \
  --opt com.docker.network.driver.mtu=1450 \
  --opt "com.docker.network.bridge.enable_icc=true" \
  --opt "com.docker.network.bridge.enable_ip_masquerade=true" \
  xr-network

Traefik Performance Tuning:

Add to traefik.yml:

global:
  checknewversion: false
  sendanonymoususage: false

serversTransport:
  insecureSkipVerify: true
  maxIdleConnsPerHost: 100
  forwardingTimeouts:
    dialTimeout: 30s
    responseHeaderTimeout: 10s

Container Resource Limits

services:
  xr-app:
    deploy:
      resources:
        limits:
          cpus: '4'
          memory: 4G
        reservations:
          cpus: '2'
          memory: 2G

Security Considerations

Traefik Middlewares for XR Endpoints

Security Headers:

http:
  middlewares:
    security-headers:
      headers:
        browserXssFilter: true
        contentTypeNosniff: true
        frameDeny: true
        sslRedirect: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customFrameOptionsValue: "SAMEORIGIN"

Authentication for Development:

middlewares:
  dev-auth:
    basicAuth:
      users:
        - "admin:$$apr1$$hashhere"
      realm: "XR Development Environment"

CrowdSec Security Integration

Install CrowdSec Agent:

curl -s https://install.crowdsec.net | sudo sh
sudo cscli collection install crowdsecurity/traefik
sudo cscli hub update
sudo systemctl restart crowdsec

Configure CrowdSec Remediation:

middlewares:
  crowdsec-bouncer:
    plugin:
      pluginName: ""
      pluginConf:
        crowdsec:
          enabled: true
          machineID: ""
          crowdsecLapiKey: ""
          updateFrequencySeconds: 10

Deployment Workflow

CI/CD Pipeline Setup

.gitlab-ci.yml:

stages:
  - test
  - build
  - deploy

test:
  stage: test
  image: node:18
  script:
    - npm install
    - npm run test

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

deploy:
  stage: deploy
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker-compose pull
    - docker-compose up -d
  only:
    - main

Automated Testing with XR Emulators

Test XR Functionality:

// tests/xr-test.js
const { expect } = require('chai');

describe('WebXR Functionality', () => {
  it('should support VR sessions', async () => {
    const xr = navigator.xr;
    const isSupported = await xr.isSessionSupported('immersive-vr');
    expect(isSupported).to.be.true;
  });

  it('should initialize Three.js scene', () => {
    const scene = new THREE.Scene();
    expect(scene).to.be.instanceOf(THREE.Scene);
  });
});

Troubleshooting

Common GPU Container Issues

Issue: GPU not detected in container

Solution:

# Verify NVIDIA driver
nvidia-smi

# Check NVIDIA runtime
docker info | grep nvidia

# Test GPU access
docker run --rm --gpus all nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi

Issue: Out of GPU memory

Solution:

  • Reduce model complexity
  • Use texture compression
  • Implement LOD (Level of Detail)
  • Optimize shader code

WebXR Browser Compatibility

Browser Support Matrix:

Browser VR AR Notes
Chrome 84+ ✅ (Android) Full WebXR support
Firefox 88+ VR only
Edge 84+ ✅ (Android) Chromium-based
Safari 15+ Limited support

WebSocket Connection Problems

Debug WebSocket Connections:

const ws = new WebSocket('wss://ws.your-domain.com');

ws.onopen = () => {
  console.log('WebSocket connected');
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = (event) => {
  console.log('WebSocket closed:', event.code, event.reason);
};

Appendix

Sample Docker Compose Files

Complete XR Development Stack:

version: '3.8'

services:
  # Traefik reverse proxy
  traefik:
    image: traefik:v3.6
    container_name: traefik
    restart: unless-stopped
    # ... (see above for full config)

  # Three.js development server
  threejs-dev:
    image: node:18-alpine
    container_name: threejs-dev-server
    restart: unless-stopped
    # ... (see above for full config)

  # WebSocket backend
  websocket-server:
    image: node:18-alpine
    container_name: webxr-websocket
    restart: unless-stopped
    # ... (see above for full config)

  # Redis for session management
  redis:
    image: redis:7-alpine
    container_name: webxr-redis
    restart: unless-stopped
    # ... (see above for full config)

  # Monitoring
  prometheus:
    image: prom/prometheus:latest
    container_name: xr-prometheus
    restart: unless-stopped
    # ... (see above for full config)

  grafana:
    image: grafana/grafana:latest
    container_name: xr-grafana
    restart: unless-stopped
    # ... (see above for full config)

Complete Traefik Configuration

See above sections for complete traefik.yml and middleware configurations.

Troubleshooting Checklist

  • Verify Docker daemon is running
  • Check NVIDIA driver installation
  • Confirm GPU runtime is available
  • Verify Traefik is accessible
  • Check SSL certificates are valid
  • Test WebSocket connections
  • Monitor container logs
  • Verify network connectivity
  • Check resource usage
  • Review CrowdSec security logs

Conclusion

This comprehensive XR development environment provides everything you need to build modern WebXR applications with complete control over your infrastructure. By leveraging Docker for containerization and Traefik for reverse proxying, you get:

  • Scalable architecture that grows with your projects
  • Professional tooling for team collaboration
  • Security features to protect your development environment
  • Performance optimizations for XR workloads
  • Monitoring and observability for production-ready deployments

The self-hosted approach ensures privacy, control, and the flexibility to customize every component to your specific needs. Whether you're building VR experiences, AR applications, or mixed reality solutions, this infrastructure provides a solid foundation for professional XR development.

Next Steps

  1. Start with the basic project template and gradually add complexity
  2. Implement CI/CD pipelines for automated testing and deployment
  3. Add monitoring dashboards to track performance and resource usage
  4. Experiment with GPU acceleration for compute-intensive XR applications
  5. Scale up as your projects and team grow

Happy XR development! 🚀