Overview
Dockhand is built with a modern, security-focused architecture that emphasizes performance, maintainability, and minimal dependencies. The application follows a server-side rendering (SSR) approach with progressive enhancement for optimal user experience.
Dockhand runs on a custom Wolfi-based OS built from scratch using apko, with every package explicitly declared for maximum security.
Architecture Diagram
┌─────────────────────────────────────────────────────────────┐
│ Browser / Client │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Svelte 5 Components + SvelteKit 2 Frontend │ │
│ │ - shadcn-svelte UI components │ │
│ │ - TailwindCSS styling │ │
│ │ - xterm.js for terminals │ │
│ │ - CodeMirror for editors │ │
│ │ - Cytoscape.js for graphs │ │
│ └───────────────────────────────────────────────────────┘ │
└──────────────────┬──────────────────────────────────────────┘
│ HTTP/WebSocket/SSE
┌──────────────────▼──────────────────────────────────────────┐
│ Dockhand Application Server │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ SvelteKit API Routes (Backend) │ │
│ │ - Request handling & validation │ │
│ │ - Authentication & authorization │ │
│ │ - Business logic orchestration │ │
│ └────┬────────────────────────────────────────────┬─────┘ │
│ │ │ │
│ ┌────▼─────────────┐ ┌────────▼──────┐ │
│ │ Server Modules │ │ Bun Runtime │ │
│ │ - docker.ts │ │ - Fast JS │ │
│ │ - stacks.ts │ │ - Native TS │ │
│ │ - db.ts │ │ - Built-in │ │
│ │ - auth.ts │ │ fetch │ │
│ │ - git.ts │ └───────────────┘ │
│ │ - scheduler.ts │ │
│ └────┬─────────────┘ │
│ │ │
│ ┌────▼──────────────────────────────────────────────────┐ │
│ │ Drizzle ORM Layer │ │
│ │ - Type-safe database queries │ │
│ │ - SQLite or PostgreSQL support │ │
│ └────┬──────────────────────────────────────────────────┘ │
│ │ │
│ ┌────▼────────┐ │
│ │ SQLite DB │ or ┌──────────────────┐ │
│ │ (default) │ │ PostgreSQL DB │ │
│ └─────────────┘ └──────────────────┘ │
└──────────────────┬──────────────────────────────────────────┘
│ Docker API (Unix socket / TCP)
┌──────────────────▼──────────────────────────────────────────┐
│ Docker Engine(s) │
│ - Local Unix socket: /var/run/docker.sock │
│ - Remote TCP: https://remote-host:2376 │
│ - Hawser agents: Edge or standard deployment │
└──────────────────────────────────────────────────────────────┘
Frontend Architecture
The frontend is built with modern web technologies for maximum performance and developer experience.
SvelteKit 2 & Svelte 5
Core framework features:
Server-Side Rendering (SSR) : Initial page loads are rendered on the server for fast first paint
Progressive Enhancement : Works without JavaScript, enhanced when available
File-based Routing : Routes defined by file structure in src/routes/
API Routes : Backend endpoints colocated with frontend pages
Type Safety : Full TypeScript support with generated types
// Example: SvelteKit load function with type safety
import type { PageLoad } from './$types' ;
export const load : PageLoad = async ({ fetch , params }) => {
const response = await fetch ( `/api/containers/ ${ params . id } ` );
const container = await response . json ();
return { container };
};
Svelte 5 Runes
Dockhand uses Svelte 5’s new reactivity system:
< script lang = "ts" >
// State management with runes
let containers = $ state < Container []>([]);
let loading = $ state ( true );
let filter = $ state ( '' );
// Derived state
const filteredContainers = $ derived . by (() => {
return containers . filter ( c => c . name . includes ( filter ));
});
// Effects for side effects
$ effect (() => {
if ( browser ) {
fetchContainers ();
}
});
</ script >
UI Components
Built with shadcn-svelte and TailwindCSS:
shadcn-svelte Accessible component library with bits-ui primitives for modals, dropdowns, tooltips, etc.
TailwindCSS v4 Utility-first CSS with Vite plugin for optimal performance
Lucide Icons Consistent iconography with lucide-svelte
Mode Watcher Dark/light theme with system preference detection
Specialized Components
Terminal Emulator (xterm.js):
import { Terminal } from '@xterm/xterm' ;
import { FitAddon } from '@xterm/addon-fit' ;
import { WebLinksAddon } from '@xterm/addon-web-links' ;
const term = new Terminal ({ theme , cursorBlink: true });
const fitAddon = new FitAddon ();
term . loadAddon ( fitAddon );
term . loadAddon ( new WebLinksAddon ());
Code Editor (CodeMirror 6):
import { EditorView } from '@codemirror/view' ;
import { yaml } from '@codemirror/lang-yaml' ;
import { oneDark } from '@codemirror/theme-one-dark' ;
const editor = new EditorView ({
extensions: [
yaml (),
oneDark ,
// ... other extensions
]
});
Dependency Graph (Cytoscape.js):
import cytoscape from 'cytoscape' ;
const cy = cytoscape ({
container: element ,
elements: [
{ data: { id: 'web' , label: 'web' } },
{ data: { id: 'db' , label: 'postgres' } },
{ data: { source: 'web' , target: 'db' } }
],
layout: { name: 'cose' }
});
Backend Architecture
The backend is built with SvelteKit API routes and modular server-side code.
Bun Runtime
Dockhand uses Bun as the JavaScript runtime:
Fast execution : Optimized JavaScript and TypeScript engine
Native TypeScript : No transpilation required
Built-in fetch : Node.js fetch API for HTTP requests
WebSocket support : Native WebSocket implementation
Compatibility : Node.js API compatibility layer
The production Dockerfile uses Node.js 24 instead of Bun to avoid BoringSSL memory leaks on mTLS connections. Development can use either.
Server Module Structure
src/lib/server/
├── auth.ts # Authentication (Argon2id, OIDC, LDAP)
├── authorize.ts # RBAC authorization checks
├── docker.ts # Direct Docker API client
├── stacks.ts # Compose stack management
├── git.ts # Git repository operations
├── db.ts # Database operations wrapper
├── encryption.ts # AES-256-GCM credential encryption
├── scheduler/ # Croner-based job scheduling
├── audit.ts # Audit logging
├── notifications.ts # Email/webhook/MQTT notifications
├── scanner.ts # Trivy/Grype integration
├── hawser.ts # Hawser remote agent protocol
└── subprocess-manager.ts # Go collector process management
Docker API Integration
Direct API calls without dockerode:
// src/lib/server/docker.ts
export async function listContainers (
all : boolean = false ,
envId ?: number
) : Promise < ContainerInfo []> {
const env = await getEnvironment ( envId );
const endpoint = buildEndpoint ( env , '/containers/json' );
const response = await fetch ( endpoint , {
method: 'GET' ,
agent: getAgent ( env ), // Unix socket or HTTP agent
headers: { 'Content-Type' : 'application/json' }
});
if ( ! response . ok ) {
throw new DockerConnectionError ( 'Failed to list containers' , response );
}
return response . json ();
}
Connection Types :
Unix Socket
Local Docker socket at /var/run/docker.sock using custom agent
TCP/TLS
Remote Docker daemon via HTTPS with optional mTLS client certificates
Hawser
WebSocket-based protocol for edge deployments and NAT traversal
Compose Stack Management
All stack operations use docker compose commands:
// src/lib/server/stacks.ts
export async function deployStack (
options : DeployStackOptions
) : Promise < StackOperationResult > {
const { name , compose , envId , envVars } = options ;
// Write compose.yaml to disk
const stackDir = getStackDir ( name );
writeFileSync ( join ( stackDir , 'compose.yaml' ), compose );
// Prepare environment variables
const env = { ... process . env , ... envVars };
// Execute docker compose up
const result = await executeCompose (
[ 'up' , '-d' , '--remove-orphans' ],
{ cwd: stackDir , env , envId }
);
return result ;
}
Stack Storage :
Internal : ~/.dockhand/stacks/{stackName}/compose.yaml
Git : ~/.dockhand/git/{repoId}/{stackName}/compose.yaml
External : Detected via Docker labels, not stored
Authentication System
Multi-provider authentication with security best practices:
// src/lib/server/auth.ts
import argon2 from 'argon2' ;
export async function hashPassword ( password : string ) : Promise < string > {
return argon2 . hash ( password , {
type: argon2 . argon2id , // Memory-hard, timing-attack resistant
memoryCost: 65536 , // 64 MB
timeCost: 3 , // 3 iterations
parallelism: 4 // 4 threads
});
}
export async function verifyPassword (
hash : string ,
password : string
) : Promise < boolean > {
return argon2 . verify ( hash , password );
}
export async function createSession ( userId : number ) : Promise < string > {
// Generate cryptographically secure 32-byte token
const token = Buffer . from ( secureRandomBytes ( 32 )). toString ( 'hex' );
await dbCreateSession ({
userId ,
token ,
expiresAt: new Date ( Date . now () + 30 * 24 * 60 * 60 * 1000 ) // 30 days
});
return token ;
}
Cookie Configuration :
const cookieOptions = {
httpOnly: true , // Prevents XSS
secure: isProduction , // HTTPS only in production
sameSite: 'strict' , // CSRF protection
path: '/' ,
maxAge: 30 * 24 * 60 * 60 // 30 days
};
Authorization (RBAC)
Role-Based Access Control for Enterprise edition:
// src/lib/server/authorize.ts
export interface AuthContext {
user ?: User ;
roles : Role [];
permissions : Permissions ;
authEnabled : boolean ;
isEnterprise : boolean ;
can ( resource : string , action : string , envId ?: number ) : Promise < boolean >;
canAccessEnvironment ( envId : number ) : Promise < boolean >;
}
export async function authorize ( cookies : Cookies ) : Promise < AuthContext > {
const sessionToken = cookies . get ( SESSION_COOKIE_NAME );
if ( ! sessionToken ) {
return createGuestContext ();
}
const session = await getSession ( sessionToken );
if ( ! session ) {
return createGuestContext ();
}
const user = await getUser ( session . userId );
const roles = await getUserRoles ( user . id );
const permissions = mergePermissions ( roles );
return createAuthContext ( user , roles , permissions );
}
Database Layer
Type-safe database operations with Drizzle ORM.
Drizzle ORM
Modern TypeScript ORM with zero-runtime overhead:
// src/lib/server/db/drizzle.ts
import { drizzle } from 'drizzle-orm/better-sqlite3' ;
import Database from 'better-sqlite3' ;
import * as schema from './schema' ;
const sqlite = new Database ( dbPath );
export const db = drizzle ( sqlite , { schema });
// Type-safe queries
export async function getEnvironment ( id : number ) {
return db . query . environments . findFirst ({
where: eq ( schema . environments . id , id )
});
}
export async function listContainers ( envId : number ) {
return db . query . containerEvents . findMany ({
where: eq ( schema . containerEvents . environmentId , envId ),
orderBy: desc ( schema . containerEvents . timestamp )
});
}
Schema Design
Database tables are defined using Drizzle’s schema builder:
// Example: environments table
export const environments = sqliteTable ( 'environments' , {
id: integer ( 'id' ). primaryKey ({ autoIncrement: true }),
name: text ( 'name' ). notNull (),
connectionType: text ( 'connection_type' ). notNull (),
socketPath: text ( 'socket_path' ),
host: text ( 'host' ),
port: integer ( 'port' ),
tls: integer ( 'tls' , { mode: 'boolean' }). default ( false ),
tlsCert: text ( 'tls_cert' ), // PEM format
tlsKey: text ( 'tls_key' ), // PEM format (encrypted)
tlsCa: text ( 'tls_ca' ), // PEM format
tlsSkipVerify: integer ( 'tls_skip_verify' , { mode: 'boolean' }),
collectActivity: integer ( 'collect_activity' , { mode: 'boolean' }). default ( true ),
collectMetrics: integer ( 'collect_metrics' , { mode: 'boolean' }). default ( true ),
icon: text ( 'icon' ). default ( 'globe' ),
labels: text ( 'labels' , { mode: 'json' }). $type < string []>(),
createdAt: text ( 'created_at' ). notNull (),
updatedAt: text ( 'updated_at' ). notNull ()
});
Migration System
Database migrations managed by Drizzle Kit:
# Generate migration from schema changes
bun drizzle-kit generate:sqlite
# Apply migrations
bun drizzle-kit push:sqlite
Migrations are stored in:
drizzle/ for SQLite
drizzle-pg/ for PostgreSQL
Migrations run automatically on application startup via hooks.server.ts.
Database Support
Using better-sqlite3 for synchronous API and performance. Stored at $DATA_DIR/dockhand.db.
Using postgres package for production deployments. Configured via DATABASE_URL environment variable.
Base Operating System
Custom Wolfi-based OS built with apko for security and minimalism.
Wolfi OS
Wolfi is a Linux distribution designed for containers:
Minimal attack surface : Only necessary packages included
CVE-free : Packages rebuilt to eliminate known vulnerabilities
No shell required : Distroless by default
APK packages : Fast package management
Regular updates : Chainguard maintains the repository
apko Build Process
The Dockerfile uses apko to generate a custom OS:
# Stage 1: OS Generator
FROM alpine:3.21 AS os-builder
# Generate apko.yaml with explicit packages
RUN printf '%s \n ' \
"contents:" \
" repositories:" \
" - https://packages.wolfi.dev/os" \
" keyring:" \
" - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub" \
" packages:" \
" - wolfi-base" \
" - ca-certificates" \
" - busybox" \
" - docker-cli" \
" - docker-compose" \
" - git" \
" - openssh-client" \
" - postgresql-client" \
" - sqlite" \
> apko.yaml
# Build OS tarball
RUN apko build apko.yaml dockhand-base:latest output.tar
Packages Included :
wolfi-base: Core system files
ca-certificates: TLS certificate trust store
busybox: Essential Unix utilities
docker-cli, docker-compose: Docker management
git, openssh-client: Git operations
postgresql-client, sqlite: Database clients
tini: Init system for proper signal handling
su-exec: User switching for PUID/PGID support
Multi-Stage Build
# Stage 2: Application Builder
FROM node:24-slim AS app-builder
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm ci --omit=dev # Production dependencies only
# Stage 3: Final Image
FROM scratch
COPY --from=os-builder /work/rootfs/ /
COPY --from=app-builder /usr/local/bin/node /usr/local/bin/node
COPY --from=app-builder /app/build ./build
COPY --from=app-builder /app/node_modules ./node_modules
The final image is built from scratch with only the custom OS and application files. No base image vulnerabilities.
Real-Time Communication
Dockhand uses multiple protocols for real-time updates.
Server-Sent Events (SSE)
One-way server-to-client streaming:
// src/lib/server/sse.ts
export function createSSEStream (
request : Request ,
listener : ( send : ( data : any ) => void ) => () => void
) : Response {
const stream = new ReadableStream ({
start ( controller ) {
const send = ( data : any ) => {
const message = `data: ${ JSON . stringify ( data ) } \n\n ` ;
controller . enqueue ( new TextEncoder (). encode ( message ));
};
const cleanup = listener ( send );
// Cleanup on close
request . signal . addEventListener ( 'abort' , () => {
cleanup ();
controller . close ();
});
}
});
return new Response ( stream , {
headers: {
'Content-Type' : 'text/event-stream' ,
'Cache-Control' : 'no-cache' ,
'Connection' : 'keep-alive'
}
});
}
Use Cases :
Dashboard real-time stats
Log streaming
Deployment progress
Event notifications
WebSocket
Bi-directional communication for interactive features:
// Example: Terminal WebSocket endpoint
import { WebSocketServer } from 'ws' ;
export const websocket = {
handler : ( ws : WebSocket , request : Request ) => {
const { containerId } = parseUrl ( request . url );
// Exec into container
const exec = await dockerExec ( containerId , {
Cmd: [ '/bin/sh' ],
AttachStdin: true ,
AttachStdout: true ,
AttachStderr: true ,
Tty: true
});
// Pipe exec stream to WebSocket
exec . stdout . on ( 'data' , data => ws . send ( data ));
ws . on ( 'message' , data => exec . stdin . write ( data ));
ws . on ( 'close' , () => exec . kill ());
}
};
Use Cases :
Interactive terminals
Container exec sessions
Real-time collaborative editing (future)
Monitoring & Metrics
Optional metrics collection using a separate Go process.
Collection Worker
Written in Go for performance:
// collector/main.go
package main
func collectMetrics ( ctx context . Context , envId int ) {
client := createDockerClient ()
for {
select {
case <- ctx . Done ():
return
case <- time . After ( 5 * time . Second ):
containers := listContainers ( client )
for _ , container := range containers {
stats := getContainerStats ( client , container . ID )
storeMetrics ( envId , container . ID , stats )
}
}
}
}
Collected Metrics :
CPU percentage (calculated from CPU delta)
Memory usage (with cache separation)
Network RX/TX bytes
Block I/O read/write bytes
Metrics Storage
In-memory storage with configurable retention:
// src/lib/server/metrics-store.ts
interface MetricsStore {
[ envId : number ] : {
[ containerId : string ] : ContainerStats [];
};
}
const metrics : MetricsStore = {};
const MAX_POINTS = 360 ; // 30 minutes at 5-second intervals
export function storeMetric ( envId : number , containerId : string , stats : ContainerStats ) {
if ( ! metrics [ envId ]) metrics [ envId ] = {};
if ( ! metrics [ envId ][ containerId ]) metrics [ envId ][ containerId ] = [];
metrics [ envId ][ containerId ]. push ( stats );
// Keep only last MAX_POINTS
if ( metrics [ envId ][ containerId ]. length > MAX_POINTS ) {
metrics [ envId ][ containerId ]. shift ();
}
}
Security Architecture
Security is built into every layer of Dockhand.
Threat Model
Authentication
Prevent unauthorized access with Argon2id hashing, secure session tokens, and MFA support
Authorization
Enforce least privilege with RBAC, environment-level permissions, and resource-level checks
Data Protection
Encrypt sensitive data (credentials, certificates) with AES-256-GCM
Network Security
Use TLS for all remote connections, validate certificates, support mTLS
Container Isolation
Run as non-root user, drop capabilities, use read-only filesystem where possible
Encryption
Credentials and secrets are encrypted at rest:
// src/lib/server/encryption.ts
import { randomBytes , createCipheriv , createDecipheriv } from 'node:crypto' ;
const ALGORITHM = 'aes-256-gcm' ;
const KEY_LENGTH = 32 ;
const IV_LENGTH = 16 ;
const AUTH_TAG_LENGTH = 16 ;
export function encrypt ( plaintext : string , key : Buffer ) : string {
const iv = randomBytes ( IV_LENGTH );
const cipher = createCipheriv ( ALGORITHM , key , iv );
let ciphertext = cipher . update ( plaintext , 'utf8' , 'hex' );
ciphertext += cipher . final ( 'hex' );
const authTag = cipher . getAuthTag ();
// Format: iv:authTag:ciphertext
return ` ${ iv . toString ( 'hex' ) } : ${ authTag . toString ( 'hex' ) } : ${ ciphertext } ` ;
}
export function decrypt ( encrypted : string , key : Buffer ) : string {
const [ ivHex , authTagHex , ciphertext ] = encrypted . split ( ':' );
const iv = Buffer . from ( ivHex , 'hex' );
const authTag = Buffer . from ( authTagHex , 'hex' );
const decipher = createDecipheriv ( ALGORITHM , key , iv );
decipher . setAuthTag ( authTag );
let plaintext = decipher . update ( ciphertext , 'hex' , 'utf8' );
plaintext += decipher . final ( 'utf8' );
return plaintext ;
}
Encrypted Fields :
Docker registry passwords
Git SSH keys and tokens
LDAP bind passwords
OIDC client secrets
TLS private keys
Environment variables marked as secrets
Session Security
// Session token generation
function generateSessionToken () : string {
const bytes = secureRandomBytes ( 32 ); // 32 bytes = 256 bits
return Buffer . from ( bytes ). toString ( 'hex' ); // 64 hex characters
}
// Session validation
async function validateSession ( token : string ) : Promise < Session | null > {
const session = await getSession ( token );
if ( ! session ) return null ;
if ( new Date () > new Date ( session . expiresAt )) {
await deleteSession ( session . id );
return null ;
}
return session ;
}
Audit Logging
All privileged actions are logged:
// src/lib/server/audit.ts
export async function auditContainer (
userId : number ,
action : string ,
containerId : string ,
envId : number ,
details ?: object
) {
await createAuditLog ({
userId ,
action: `container. ${ action } ` ,
resourceType: 'container' ,
resourceId: containerId ,
environmentId: envId ,
details ,
timestamp: new Date (). toISOString (),
success: true
});
}
Response Compression
Automatic gzip compression for responses:
// src/hooks.server.ts
function shouldCompress ( request : Request , response : Response ) : boolean {
const acceptEncoding = request . headers . get ( 'accept-encoding' ) || '' ;
if ( ! acceptEncoding . includes ( 'gzip' )) return false ;
const contentType = response . headers . get ( 'content-type' ) || '' ;
const isCompressible = COMPRESSIBLE_TYPES . some ( type =>
contentType . includes ( type )
);
return isCompressible ;
}
async function compressResponse ( response : Response ) : Promise < Response > {
const body = await response . arrayBuffer ();
const compressed = gzipSync ( new Uint8Array ( body ));
const headers = new Headers ( response . headers );
headers . set ( 'content-encoding' , 'gzip' );
headers . delete ( 'content-length' );
return new Response ( compressed , { headers });
}
Database Indexing
Optimized indexes for common queries:
CREATE INDEX idx_container_events_env_time
ON container_events(environment_id, timestamp DESC );
CREATE INDEX idx_audit_logs_user_time
ON audit_logs(user_id, timestamp DESC );
CREATE INDEX idx_sessions_token
ON sessions (token);
Caching Strategy
In-memory caching : Environment list, user permissions
Conditional requests : ETags for static resources
Stale-while-revalidate : Background data refresh
Deployment Architecture
Single Container Deployment
Simplest deployment with SQLite:
version : '3.8'
services :
dockhand :
image : dockhand/dockhand:latest
ports :
- "3000:3000"
volumes :
- /var/run/docker.sock:/var/run/docker.sock
- dockhand-data:/app/data
environment :
PUID : 1001
PGID : 1001
volumes :
dockhand-data :
PostgreSQL Deployment
For production with external database:
version : '3.8'
services :
dockhand :
image : dockhand/dockhand:latest
ports :
- "3000:3000"
volumes :
- /var/run/docker.sock:/var/run/docker.sock
environment :
DATABASE_URL : postgresql://user:pass@postgres:5432/dockhand
PUID : 1001
PGID : 1001
depends_on :
- postgres
postgres :
image : postgres:16-alpine
volumes :
- postgres-data:/var/lib/postgresql/data
environment :
POSTGRES_DB : dockhand
POSTGRES_USER : user
POSTGRES_PASSWORD : pass
volumes :
postgres-data :
High Availability
For enterprise deployments:
Load balancer : Nginx/Traefik in front of multiple Dockhand instances
Shared PostgreSQL : Managed database (RDS, CloudSQL, etc.)
Session affinity : Sticky sessions for WebSocket connections
Health checks : /api/health endpoint for load balancer
Dockhand is designed to be stateless except for the database, making horizontal scaling straightforward.
Extension Points
Dockhand provides several extension points for customization.
Webhooks
Outgoing webhooks for notifications:
interface WebhookPayload {
event : string ; // e.g., 'container.started'
timestamp : string ;
environment : { id : number ; name : string };
resource : object ; // Container, image, stack, etc.
user ?: { id : number ; username : string };
}
// POST to webhook URL with custom headers
await fetch ( webhookUrl , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Dockhand-Signature' : signature ,
... customHeaders
},
body: JSON . stringify ( payload )
});
MQTT Integration
Publish events to MQTT broker:
import mqtt from 'mqtt' ;
const client = mqtt . connect ( brokerUrl , {
username ,
password ,
clientId: 'dockhand'
});
client . publish (
`dockhand/ ${ envName } /containers/ ${ containerId } /state` ,
JSON . stringify ({ state: 'running' , timestamp: new Date () }),
{ qos: 1 , retain: true }
);
Custom Themes
Extend Tailwind configuration:
// src/lib/themes.ts
export const customTheme = {
colors: {
primary: { /* ... */ },
secondary: { /* ... */ },
},
borderRadius: { /* ... */ },
fontFamily: { /* ... */ }
};
Future Architecture Plans
Plugin System Extensibility framework for custom integrations and UI components
GraphQL API Alternative API layer for complex queries and subscriptions
Message Queue Redis/NATS for distributed job processing
Kubernetes Support Manage Kubernetes clusters alongside Docker