Curium
Public Made by Adomby adom
Use when the user wants to create, delete, pause, resume, stop, start, or manage Adom containers via the Curium API.
Install
Use when the user wants to create, delete, pause, resume, stop, start, or manage Adom containers via the Curium API.
adom-wiki pkg install adom/curium
Latest: v1.0.0, published
Contents
- Authentication
- Container ID Format
- Container Endpoints
- List Containers
- Create Container
- Get Container
- Delete Container
- Pause Container
- Resume Container
- Stop Container
- Start Container
- Commit Container State
- Stream Container Stats
- Prune Containers
- Container Lifecycle
- Workcell Endpoints
- List Workcells
- Get Workcell
- Associate Container with Workcell
- Disassociate Container from Workcell
- Workcell WebSocket (Device Notifications)
- Networking & Routing
- Directory Structure on Host
- Resource Defaults
- Configuration (Environment Variables)
- Error Responses
- Quick Examples
Curium API -- Adom Container Management
Curium is the Adom container orchestration service. It manages Docker containers, Traefik routing, SSH portal access, and USB/IP device attachment for user project containers.
Base URL: https://curium.adom.inc (or wherever your Curium instance is hosted)
Authentication
Curium itself currently has minimal auth (TODOs in codebase). It integrates with Carbon for user/org lookups and API key creation. When calling Curium from within an Adom container, use the standard Adom API key pattern:
API_KEY=$(cat /var/run/adom/api-key)
# Then use: -b "session_token=$API_KEY" on curl calls if auth is added
Container ID Format
All containers use the format: {owner}-{repository}-{hex_hash}
Example: john-gallia-f280e93ffec7e79d
owner: project owner usernamerepository: project/repo namehex_hash: 8-byte random hex identifier
Container Endpoints
List Containers
GET /containers
Query Parameters (optional):
| Param | Type | Description |
|---|---|---|
owner |
string | Filter by owner username |
repository |
string | Filter by repository name |
Response (200):
[
{
"id": "john-gallia-f280e93ffec7e79d",
"owner": "john",
"repository": "gallia",
"unique_hash": "f280e93ffec7e79d",
"status": "running",
"services": {
"coder_url": "https://coder.john-gallia-f280e93ffec7e79d.containers.adom.inc",
"s3_url": "https://s3.john-gallia-f280e93ffec7e79d.containers.adom.inc",
"ssh_credentials": {
"hostname": "ssh.containers.adom.inc",
"port": 2222,
"username": "john-gallia-f280e93ffec7e79d"
}
}
}
]
Create Container
POST /containers
Request Body:
{
"owner": "john",
"repository": "gallia",
"actor": "john",
"resource_limits": {
"cpu_shares": 512,
"vcpu_count": 2.0,
"memory_soft_limit": 536870912,
"memory_hard_limit": 1073741824
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
owner |
string | yes | -- | Project owner username |
repository |
string | yes | -- | Project/repo name |
actor |
string | yes | -- | User performing the action |
resource_limits.cpu_shares |
int | no | 512 | Relative CPU priority |
resource_limits.vcpu_count |
float | no | 2.0 | vCPU allocation (0.1-8) |
resource_limits.memory_soft_limit |
int | no | 536870912 | Memory reservation in bytes (512 MiB) |
resource_limits.memory_hard_limit |
int | no | 1073741824 | Memory max in bytes (1 GiB). Range: 128 MiB - 8 GiB |
Response (201):
{
"id": "john-gallia-f280e93ffec7e79d",
"owner": "john",
"repository": "gallia",
"unique_hash": "f280e93ffec7e79d",
"services": { "..." }
}
What happens internally:
- Fetches user orgs from Carbon (
/users/{actor}/orgs) - Creates API key via Carbon (
/internal/containers/create-api-key) - Copies project files from
{DATA_DIR}/public/projects/{owner}/{name}/latest - Creates isolated Docker bridge network named
{container_id} - Connects Traefik to the new network
- Creates container with Traefik labels for Coder (port 8080) and S3 (port 9000)
- Mounts project dir at
/home/adom/project, API key at/var/run/adom/api-key - Starts the container
Get Container
GET /containers/{id}
Response (200): Container object with status. Returns 404 if not found.
Delete Container
DELETE /containers/{id}?commit_state={bool}
| Param | Type | Required | Description |
|---|---|---|---|
commit_state |
bool | yes | If true, saves container state before deletion |
Response (204)
When commit_state=true:
- Backs up current
latest/tohistory/{uuid}.tar.gz - Replaces
latest/with current container filesystem
Then: force-kills container, removes volumes, disconnects Traefik, deletes network.
Pause Container
POST /containers/{id}/pause
Response (204). Suspends all processes without removing the container.
Resume Container
POST /containers/{id}/resume
Response (204). Resumes a paused container.
Stop Container
POST /containers/{id}/stop
Response (204). Stops container without removing it.
Start Container
POST /containers/{id}/start
Response (204). Starts a stopped container.
Commit Container State
POST /containers/{id}/commit
Response (204). Creates a backup archive and replaces latest/ with current filesystem.
Stream Container Stats
GET /containers/{id}/stats
Without SSE: Returns a single JSON stats snapshot (Docker ContainerStatsResponse format: CPU, memory, network I/O, block I/O, PIDs).
With SSE (set Accept: text/event-stream): Streams stats as SSE events:
event: stats
data: {"cpu_stats": {...}, "memory_stats": {...}, ...}
Prune Containers
POST /containers/prune
Response (204). Cleans up:
- Non-existent containers labeled
adom-user-container - Disconnects Traefik from orphaned networks
- Deletes orphaned networks
- Removes stale working directories, Traefik configs, and TLS certs
Container Lifecycle
created --[start]--> running
running --[pause]--> paused
paused --[resume]--> running
running --[stop]--> exited
exited --[start]--> running
any --[delete]--> removed
Workcell Endpoints
Workcells are physical hardware units (e.g., Raspberry Pis) with USB devices that can be associated with containers.
List Workcells
GET /workcells
Response (200):
[
{
"workcell_id": "raspberry-pi-001",
"ip_address": "192.168.1.100",
"connected_at": "2026-03-06T12:15:30Z",
"container_id": "john-gallia-f280e93ffec7e79d",
"associated_at": "2026-03-06T12:16:45Z",
"bound_devices": [
{
"bus_id": "1-1",
"bound_at": "2026-03-06T12:16:50Z",
"dev_node": "/dev/ttyUSB0",
"subsystem": "tty",
"manufacturer": "Silicon Labs",
"product": "CP2102 USB to UART Bridge Controller",
"serial_number": "0001",
"id_vendor": 1659,
"id_product": 6015,
"...": "additional device metadata"
}
]
}
]
Get Workcell
GET /workcells/{id}
Response (200): Workcell object with bound devices. Returns 404 if not found.
Associate Container with Workcell
PUT /workcells/{id}/association
Request Body:
{
"container_id": "john-gallia-f280e93ffec7e79d"
}
Response (200): Updated workcell object.
Attaches all bound USB devices to the container via USB/IP.
Disassociate Container from Workcell
DELETE /workcells/{id}/association
Response (200): Updated workcell object (container_id becomes null).
Detaches all USB devices and cleans up USB/IP ports.
Workcell WebSocket (Device Notifications)
GET /workcells/usbip-notify (WebSocket upgrade)
Used by workcells to report device hot-plug events:
Initial handshake (client sends):
{
"workcell_id": "raspberry-pi-001",
"ip_address": "192.168.1.100"
}
Device bound (client sends):
{
"type": "device_bound",
"bus_id": "1-1",
"metadata": { "dev_node": "/dev/ttyUSB0", "manufacturer": "...", "..." }
}
Device removed (client sends):
{
"type": "device_removed",
"bus_id": "1-1"
}
Networking & Routing
Each container gets its own Docker bridge network. Traefik connects to each network and routes traffic based on hostname:
| Service | Hostname Pattern | Port |
|---|---|---|
| Coder (VS Code) | coder.{container_id}.{BASE_HOSTNAME} |
8080 |
| S3 | s3.{container_id}.{BASE_HOSTNAME} |
9000 |
| SSH | ssh.{BASE_HOSTNAME}:2222 (username = container_id) |
2222 |
Default BASE_HOSTNAME: containers.adom.inc (production) or containers.adom.localhost (dev).
TLS via Let's Encrypt on the websecure entrypoint (port 443).
Directory Structure on Host
{DATA_DIR}/
public/
containers/{owner}/{repo}/{hash}/ # Working dir -> /home/adom/project
projects/{owner}/{repo}/
latest/ # Current project files
history/{uuid}.tar.gz # Committed snapshots
molecules/{scope}/ # Shared molecule dirs
private/
container-api-keys/{owner}/{repo}/{hash} # -> /var/run/adom/api-key
traefik/dynamic-config/ # Per-container routing rules
traefik/dynamic-certificates/ # Per-container TLS certs
Resource Defaults
| Resource | Default | Range |
|---|---|---|
| CPU shares | 512 | -- |
| vCPU count | 2.0 | 0.1 - 8 |
| Memory soft limit | 512 MiB | -- |
| Memory hard limit | 1 GiB | 128 MiB - 8 GiB |
Configuration (Environment Variables)
| Var | Default | Description |
|---|---|---|
BIND_ADDR |
0.0.0.0 |
Server bind address |
PORT |
3000 |
Server port |
DATA_DIRECTORY_EXTERNAL |
-- | External data path |
DATA_DIRECTORY_INTERNAL |
-- | Internal data path |
BASE_HOSTNAME |
containers.adom.localhost |
Base hostname for routing |
USER_CONTAINER_IMAGE |
adom/user-containers/default:latest |
Docker image for containers |
DOCKER_SOCKET_PATH |
/var/run/docker.sock |
Docker socket |
KEEP_CONTAINER_WORKING_DIRECTORY |
-- | Set to 1 to preserve dirs on delete |
Error Responses
{
"code": 500,
"error": "INTERNAL_SERVER_ERROR",
"message": "descriptive error message"
}
Standard HTTP status codes: 200, 201, 204, 404, 500.
Quick Examples
Create a container:
curl -s -X POST https://curium.adom.inc/containers \
-H 'Content-Type: application/json' \
-d '{
"owner": "john",
"repository": "my-project",
"actor": "john",
"resource_limits": {
"vcpu_count": 4.0,
"memory_hard_limit": 4294967296
}
}'
Get container stats:
curl -s https://curium.adom.inc/containers/john-my-project-abc123def456/stats
Pause and resume:
curl -s -X POST https://curium.adom.inc/containers/john-my-project-abc123def456/pause
curl -s -X POST https://curium.adom.inc/containers/john-my-project-abc123def456/resume
Delete with state commit:
curl -s -X DELETE 'https://curium.adom.inc/containers/john-my-project-abc123def456?commit_state=true'
List containers for a user:
curl -s 'https://curium.adom.inc/containers?owner=john'
Associate a workcell:
curl -s -X PUT https://curium.adom.inc/workcells/raspberry-pi-001/association \
-H 'Content-Type: application/json' \
-d '{"container_id": "john-gallia-f280e93ffec7e79d"}'