Simple service for executing shell commands on dynamically provisioned Docker-based executors.
The service allows a user to:
- send a command script to execute,
- define required resources (
cpuCount,memoryMb), - check execution status.
When a command is received, the service starts a dedicated executor, waits for it to be ready, runs the command, and updates the execution status.
- Java 17
- Spring Boot 4 (Web MVC, Validation, Async)
- Docker / Docker Compose
- JUnit 5 + Mockito + MockMvc
The same JAR runs in two modes depending on the active Spring profile:
-
Manager mode (default profile, port
8080)- exposes public API:
POST /executions,GET /executions/{id} - starts one dedicated executor container per execution
- tracks execution lifecycle in memory
- exposes public API:
-
Executor mode (
executorprofile, port8081)- internal endpoints:
GET /ready,POST /execute - executes shell command with
bash -c ...
- internal endpoints:
Execution flow:
- Client sends
POST /executions. - Manager creates execution with
QUEUEDstatus. - Manager starts executor container with requested CPU/RAM; sets status to
IN_PROGRESSand recordsstartedAt. - Manager polls executor
/ready(up to 10 retries × 500 ms = 5 s max). - Manager sends command to executor
/execute(30-second command timeout; exit code124on timeout). - Manager updates execution to
FINISHED(exit code 0) orFAILED(non-zero / timeout / error) and recordsfinishedAt. - Manager stops executor container (always, even on failure).
POST /executions
Request body:
{
"command": "echo hello",
"cpuCount": 2,
"memoryMb": 512
}Validation:
command: required, not blankcpuCount: required, min1memoryMb: required, min1
Response:
201 Created
Example response:
{
"id": "4f4eaf52-2f1d-4d89-bad3-7024a5b90f7a",
"command": "echo hello",
"cpuCount": 2,
"memoryMb": 512,
"status": "QUEUED",
"output": null,
"error": null,
"createdAt": "2026-03-22T16:00:00",
"startedAt": null,
"finishedAt": null
}GET /executions/{id}
Response:
200 OKwith current execution snapshot,404 Not Foundif ID does not exist.
Example (finished):
{
"id": "4f4eaf52-2f1d-4d89-bad3-7024a5b90f7a",
"command": "echo hello",
"cpuCount": 2,
"memoryMb": 512,
"status": "FINISHED",
"output": "hello\n",
"error": null,
"createdAt": "2026-03-22T16:00:00",
"startedAt": "2026-03-22T16:00:01",
"finishedAt": "2026-03-22T16:00:02"
}Example (failed — non-zero exit code):
{
"id": "7a1bcf03-9e2d-4f11-abc1-3318b4c70e52",
"command": "exit 1",
"cpuCount": 1,
"memoryMb": 256,
"status": "FAILED",
"output": "",
"error": "Command failed with exit code 1",
"createdAt": "2026-03-22T16:05:00",
"startedAt": "2026-03-22T16:05:01",
"finishedAt": "2026-03-22T16:05:02"
}The error field is populated from:
- command stderr (if non-empty),
- or
"Command failed with exit code <N>"fallback, - or
"Command timed out after 30 seconds"when the 30-second timeout is exceeded (exit code124).
QUEUEDIN_PROGRESSFINISHEDFAILED
Prerequisites:
- Docker Desktop / Docker Engine running
- Java 17+
docker compose down --remove-orphans
./mvnw clean package
docker compose up --build -ddocker compose logs -f managerdocker compose downCreate execution:
curl -X POST http://localhost:8080/executions \
-H "Content-Type: application/json" \
-d '{"command":"echo hello","cpuCount":2,"memoryMb":512}'Read execution:
curl http://localhost:8080/executions/<execution-id>src/main/resources/application.properties:
executor.image=executor-service:latestexecutor.network=executor-net
docker-compose.yml maps them via env vars:
EXECUTOR_IMAGEEXECUTOR_NETWORK
Run all tests:
./mvnw testTests cover:
- execution creation and async scheduling,
- async success and failure paths,
- readiness failure path,
- controller validation (
400), - controller HTTP contract (
201,404), - timestamps in API payload.
| Timeout | Value | Notes |
|---|---|---|
| Executor readiness | 5 s (10 × 500 ms) | Manager retries /ready; fails execution if not ready in time |
| Command execution | 30 s | Executor force-kills the process; returns exit code 124 |
| HTTP connect | 2 s | Manager -> executor connection |
| HTTP read | 45 s | Manager waiting for executor response (must exceed command timeout) |
- Execution data is kept in memory. No database, data is lost on restart.
- Executor runs arbitrary shell commands, so this is intended for local/demo use only.
- Don't expose executor endpoints (
/ready,/execute) publicly. - Each execution gets its own container, removed automatically after the command finishes.