A lightweight, educational container orchestrator built from scratch in Go. Inspired by Kubernetes, it features a master controller with REST API, worker agents that manage containers via Docker, and a CLI tool.
- Master-Worker Architecture: Distributed container management
- Multi-Container Pods: Kubernetes-style pods with shared lifecycle
- REST API: Echo-based HTTP server for control plane
- Web Dashboard: Modern React UI with real-time monitoring
- Persistent Storage: PostgreSQL or in-memory state store
- Health Checks: Liveness and readiness probes (HTTP, TCP, Exec)
- Hot Reloading: Air integration for rapid development
- Production Patterns: Following golang-standards/project-layout
- Go 1.25 or later
- Docker Engine
- Node.js 18+ (for web dashboard)
- Make (optional, for convenience)
- PostgreSQL 12+ (optional, for persistent storage)
# Clone the repository
git clone https://github.com/danpasecinic/podling.git
cd podling
# Install dependencies
go mod download
# Install development tools (Air, linters)
make install-tools# Start PostgreSQL using docker-compose
docker-compose up -d
# Configure via .env file (easiest method)
cp .env.example .env
# Edit .env to set STORE_TYPE=postgres
make runThe master automatically loads .env if present, so no need to export variables manually.
# Run with hot reloading
make dev
# Or run directly
make run
# Run tests
make test
# Run tests with coverage
make test-coverage
# Run tests with race detector
make test-race
# Run PostgreSQL tests (requires running database)
export TEST_DATABASE_URL="postgres://podling:podling123@localhost:5432/podling?sslmode=disable"
go test ./internal/master/state/Following the golang-standards/project-layout:
podling/
├── cmd/ # Main applications
│ ├── master/ # Master controller entry point
│ ├── worker/ # Worker agent entry point
│ └── podling/ # CLI tool entry point
├── internal/ # Private application code
│ ├── types/ # Core data models
│ │ ├── task.go # Task model and status
│ │ ├── pod.go # Pod and Container models
│ │ └── node.go # Node model and status
│ ├── master/ # Master controller internals
│ │ ├── api/ # HTTP API handlers (Echo)
│ │ ├── scheduler/ # Task and pod scheduling logic
│ │ └── state/ # State management
│ │ └── migrations/ # Database migrations
│ └── worker/ # Worker agent internals
│ ├── agent/ # Worker agent and pod executor
│ ├── docker/ # Docker SDK integration
│ └── health/ # Health check implementations
├── web/ # Web dashboard (React)
│ ├── src/
│ │ ├── api/ # API client and types
│ │ ├── components/ # UI components
│ │ ├── hooks/ # React Query hooks
│ │ ├── pages/ # Page components
│ │ └── lib/ # Utilities
│ └── package.json
├── docs/ # Documentation
│ ├── postman/ # Postman collection for API testing
│ ├── POSTMAN_GUIDE.md # API testing guide
│ └── SESSION_STATE.md # Development session tracking
├── .air.toml # Air hot reload configuration
├── Makefile # Development commands
└── go.mod # Go module definition
| Component | Responsibility | Port |
|---|---|---|
| Master | Task scheduling, API server, state management | 8080 |
| Worker | Container execution, status reporting, heartbeats | 8081+ |
| CLI | User interface for task submission and monitoring | - |
| Dashboard | Web UI for monitoring and management | 5173 |
Backend:
- Language: Go 1.25
- Web Framework: Echo v4 - High performance, minimalist
- Container Runtime: Docker Engine API
- Hot Reload: Air - Live reload for Go apps
- Testing: Go's built-in testing with race detector
Frontend:
- Framework: React 19 with TypeScript
- Build Tool: Vite - Next generation frontend tooling
- UI Components: shadcn/ui - Beautifully designed components
- Styling: Tailwind CSS v4 - Utility-first CSS
- Data Fetching: TanStack Query - Powerful async state management
The master controller exposes a RESTful API for managing tasks and worker nodes.
# Build and run the master
go build -o bin/podling-master ./cmd/master
./bin/podling-master
# Or use Make
make build && ./bin/podling-masterThe master will start on http://localhost:8080 with the following endpoints:
Import the provided Postman collection to test all endpoints:
- Import
docs/postman/Podling.postman_collection.jsoninto Postman - Import
docs/postman/Podling.postman_environment.jsonfor local environment - Select "Podling - Local" environment
- Start testing the API
See Postman Guide for detailed testing workflow.
# Build and run a worker node
go build -o bin/podling-worker ./cmd/worker
./bin/podling-worker -node-id=worker-1 -port=8081
# Or use Make
make build && ./bin/podling-worker -node-id=worker-1
# Worker configuration options:
# -node-id: Unique worker identifier (required)
# -hostname: Worker hostname (default: localhost)
# -port: Worker port (default: 8081)
# -master-url: Master API URL (default: http://localhost:8080)
# -heartbeat-interval: Heartbeat interval (default: 30s)
# -shutdown-timeout: Graceful shutdown timeout (default: 30s)The worker will:
- Connect to the master and send periodic heartbeats
- Execute tasks in Docker containers
- Report task status back to master
- Stream container logs via API
- Handle graceful shutdown with task cleanup
GET /health
curl http://localhost:8080/healthCreate Task - Submit a new task for execution
POST /api/v1/tasks
Content-Type: application/json
{
"name": "my-nginx-task",
"image": "nginx:latest",
"env": {
"PORT": "8080"
}
}
# Example
curl -X POST http://localhost:8080/api/v1/tasks \
-H "Content-Type: application/json" \
-d '{"name":"nginx-task","image":"nginx:latest"}'List Tasks - Get all tasks
GET /api/v1/tasks
curl http://localhost:8080/api/v1/tasksGet Task - Get specific task details
GET /api/v1/tasks/{taskId}
curl http://localhost:8080/api/v1/tasks/20250119123456-abc12345Update Task Status - Update task execution status (typically called by workers)
PUT /api/v1/tasks/{taskId}/status
Content-Type: application/json
{
"status": "running",
"containerId": "docker-container-id"
}
# Example
curl -X PUT http://localhost:8080/api/v1/tasks/20250119123456-abc12345/status \
-H "Content-Type: application/json" \
-d '{"status":"running","containerId":"abc123"}'Register Node - Register a worker node
POST /api/v1/nodes/register
Content-Type: application/json
{
"hostname": "worker-1",
"port": 8081,
"capacity": 10
}
# Example
curl -X POST http://localhost:8080/api/v1/nodes/register \
-H "Content-Type: application/json" \
-d '{"hostname":"worker-1","port":8081,"capacity":10}'Node Heartbeat - Update node heartbeat
POST /api/v1/nodes/{nodeId}/heartbeat
curl -X POST http://localhost:8080/api/v1/nodes/20250119123456-xyz98765/heartbeatList Nodes - Get all registered nodes
GET /api/v1/nodes
curl http://localhost:8080/api/v1/nodesExecute Task - Execute a task on worker (called by master)
POST /api/v1/tasks/:id/execute
Content-Type: application/json
{
"task": {
"taskId": "task-id",
"name": "my-task",
"image": "nginx:latest",
"env": {"PORT": "8080"}
}
}Get Task Status - Get task execution status
GET /api/v1/tasks/:id/status
curl http://localhost:8081/api/v1/tasks/task-id/statusGet Task Logs - Stream container logs
GET /api/v1/tasks/:id/logs?tail=100
curl http://localhost:8081/api/v1/tasks/task-id/logs?tail=100Tasks progress through the following states:
pending → scheduled → running → completed/failed
- pending: Task created, awaiting scheduling
- scheduled: Task assigned to a worker node
- running: Task is executing on a worker
- completed: Task finished successfully
- failed: Task execution failed
Podling supports Kubernetes-style pods - groups of one or more containers with shared lifecycle:
Create Pod - Create a multi-container pod
POST /api/v1/pods
Content-Type: application/json
{
"name": "my-web-app",
"namespace": "production",
"labels": {
"app": "web",
"version": "1.0"
},
"containers": [
{
"name": "app",
"image": "myapp:1.0",
"env": {"PORT": "8080"}
},
{
"name": "sidecar",
"image": "nginx:latest"
}
]
}
# Example
curl -X POST http://localhost:8080/api/v1/pods \
-H "Content-Type: application/json" \
-d '{"name":"web-pod","containers":[{"name":"nginx","image":"nginx:latest"}]}'List Pods - Get all pods
GET /api/v1/pods
curl http://localhost:8080/api/v1/podsGet Pod - Get specific pod with container status
GET /api/v1/pods/{podId}
curl http://localhost:8080/api/v1/pods/20250119123456-pod123Delete Pod - Delete a pod
DELETE /api/v1/pods/{podId}
curl -X DELETE http://localhost:8080/api/v1/pods/20250119123456-pod123Pods progress through similar states:
pending → scheduled → running → succeeded/failed
- pending: Pod created, awaiting scheduling
- scheduled: Pod assigned to a worker node
- running: All containers in pod are running
- succeeded: All containers exited with code 0
- failed: One or more containers failed
The web dashboard provides a modern UI for monitoring and managing your Podling cluster.
# Install dependencies (first time only)
cd web
npm install
# Start development server
npm run devThe dashboard will be available at http://localhost:5173 and connects to the master API at http://localhost:8080.
- Overview: Cluster statistics with node, pod, task, and service counts
- Nodes: View worker nodes with resource usage (CPU/Memory)
- Pods: List and inspect multi-container pods
- Tasks: Monitor single-container task execution
- Services: View service discovery and endpoints
- Auto-refresh: Data updates every 5 seconds
cd web
npm run buildThe built files will be in web/dist/ and can be served by any static file server.
The podling CLI provides a user-friendly interface to interact with the Podling orchestrator.
# Build the CLI
make build
# The binary will be at bin/podling
# Optionally, add it to your PATH
cp bin/podling /usr/local/bin/The CLI can be configured via:
- Command-line flags:
--master http://localhost:8080 - Environment variables:
PODLING_MASTER_URL=http://localhost:8080 - Config file:
~/.podling.yaml(future enhancement)
Submit a new task to run a single container:
# Basic usage
podling run my-nginx --image nginx:latest
# With environment variables
podling run my-redis --image redis:latest --env PORT=6379 --env MODE=standalone
# With custom master URL
podling --master http://production:8080 run my-task --image alpine:latestView tasks:
# List all tasks
podling ps
# Get detailed info for a specific task
podling ps --task <task-id>
podling ps -t <task-id>View task logs:
# Get logs (last 100 lines by default)
podling logs <task-id>
# Limit output
podling logs <task-id> --tail 50Create and manage multi-container pods:
# Create a pod with a single container
podling pod create my-web --container nginx:nginx:latest
# Create a pod with multiple containers
podling pod create my-app \
--container app:myapp:1.0:PORT=8080,ENV=prod \
--container sidecar:nginx:latest \
--namespace production \
--label app=myapp \
--label version=1.0
# List all pods
podling pod list
# Get detailed pod information (shows all container statuses)
podling pod get <pod-id>
# Delete a pod
podling pod delete <pod-id>Container Specification Format:
name:image[:env1=val1,env2=val2]
Examples:
nginx:nginx:latest- Simple containerapp:myapp:1.0:PORT=8080,DB=postgres- With environment variables
View all registered worker nodes:
# List all nodes
podling nodes
# With verbose output
podling nodes --verboseOutput example:
ID HOSTNAME PORT STATUS CAPACITY TASKS LAST HEARTBEAT
worker-1 localhost 8081 online 10 2 30s ago
worker-2 localhost 8082 online 10 1 25s ago
All commands support these global flags:
--master string: Master API URL (default "http://localhost:8080")--verbose, -v: Enable verbose output--config string: Config file location (default "$HOME/.podling.yaml")--help, -h: Show help for any command
# Run all tests
make test
# Run with coverage report (generates coverage.html)
make test-coverage
# Run with race detector
make test-race
# Or use go directly
go test ./...
go test -race ./...
go test -coverprofile=coverage.out ./...Current Test Coverage:
- State management: 94.7%
- API handlers: 91.9%
- Scheduler: 100%
- Worker agent: 86.2%
- Docker client: 73.6%
- Overall: >85%
# Format code
make fmt
# Run linter (requires golangci-lint)
make lint# Build all binaries
make build
# Binaries will be in bin/
# - bin/podling-master
# - bin/podling-worker
# - bin/podlingSee LICENSE file for details.
- Inspired by Kubernetes
- Project structure follows golang-standards/project-layout
- Built with Echo framework
- Development powered by Air