Back to Playground

Self-Hosted Deployment

Deploy a secure, containerized SDK terminal with full Docker isolation for your users.

Backend Server Setup

1. Install Dependencies

package.json

npm install express ws dockerode uuid

2. Docker Terminal Server

Create a Node.js server that manages isolated Docker containers for each user session.

docker-terminal-server.js

const express = require('express');
const WebSocket = require('ws');
const Docker = require('dockerode');
const { v4: uuidv4 } = require('uuid');

const app = express();
const docker = new Docker();
const wss = new WebSocket.Server({ noServer: true });
const sessions = new Map();

const CONTAINER_CONFIG = {
    Image: 'ubuntu:22.04',
    Cmd: ['/bin/bash'],
    Tty: true,
    OpenStdin: true,
    Memory: 256 * 1024 * 1024, // 256MB limit
    HostConfig: {
        Memory: 256 * 1024 * 1024,
        CPUQuota: 50000, // 50% CPU
        PidsLimit: 1024,
        NetworkMode: 'none', // No network
        ReadonlyRootfs: true,
        SecurityOpt: ['no-new-privileges:true'],
        CapDrop: ['ALL'],
    },
    Env: [
        'TERM=xterm-256color',
        'PS1=\\[\\e[32m\\]sandbox$ \\[\\e[0m\\]',
    ]
};

async function createSandboxContainer() {
    const containerId = uuidv4().slice(0, 12);
    const container = await docker.createContainer({
        ...CONTAINER_CONFIG,
        name: `terminal-${containerId}`,
    });
    await container.start();
    return { containerId, container };
}

wss.on('connection', async (ws) => {
    const sandbox = await createSandboxContainer();
    const stream = await sandbox.container.attach({
        stream: true,
        stdin: true,
        stdout: true,
        stderr: true,
        tty: true
    });

    ws.on('message', (data) => {
        const msg = JSON.parse(data);
        if (msg.type === 'input') {
            stream.write(msg.data);
        }
    });

    stream.on('data', (chunk) => {
        ws.send(JSON.stringify({
            type: 'output',
            data: chunk.toString('utf8')
        }));
    });

    ws.on('close', async () => {
        await sandbox.container.kill();
        await sandbox.container.remove();
    });
});

const server = app.listen(3000);
server.on('upgrade', (req, socket, head) => {
    wss.handleUpgrade(req, socket, head, (ws) => {
        wss.emit('connection', ws);
    });
});

console.log('🐳 Server running on http://localhost:3000');

3. Security Hardening

Apply Docker daemon lockdown and AppArmor profiles.

security-hardening.sh

#!/bin/bash
# Docker daemon lockdown
cat > /etc/docker/daemon.json << EOF
{
    "icc": false,
    "userns-remap": "default",
    "no-new-privileges": true
}
EOF

# AppArmor profile
cat > /etc/apparmor.d/docker-terminal << EOF
profile docker-terminal flags=(attach_disconnected) {
  deny /etc/** r,
  deny /proc/[1-9]**/[ns]last_pid -> r,
  deny /sys/** r,
}
EOF

systemctl restart docker apparmor

Frontend Integration

WebSocket Terminal Client

WebTerminal.ts

class WebTerminal {
  private ws: WebSocket;
  private term: any; // xterm.js instance

  connect(url: string) {
    this.ws = new WebSocket(url);
    
    this.ws.onmessage = (event) => {
      const msg = JSON.parse(event.data);
      if (msg.type === 'output') {
        this.term.write(msg.data);
      }
    };

    this.term.onData((data: string) => {
      this.ws.send(JSON.stringify({
        type: 'input',
        data
      }));
    });
  }

  close() {
    this.ws.close();
  }
}

Security Guarantees

Network Isolation

Critical

NetworkMode: none prevents all outbound connections

Memory Limit

Critical

256MB hard cap prevents memory exhaustion

CPU Quota

High

50% CPU limit prevents resource starvation

Filesystem

High

Read-only rootfs prevents file modifications

Capabilities

Critical

CAP_DROP: ALL prevents privilege escalation

PID Limit

Medium

1024 process limit prevents fork bombs

Deployment Steps

Deploy.sh

# 1. Clone your project
git clone https://github.com/x2yDevs/x2y-playground.git
cd x2y-playground

# 2. Install dependencies
npm install

# 3. Apply security hardening
chmod +x security-hardening.sh
sudo ./security-hardening.sh

# 4. Start the server
node docker-terminal-server.js

# 5. Access at http://localhost:3000
# Each WebSocket connection creates isolated container
Built with v0