npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

coa-service-sdk

v1.4.2

Published

SDK and CLI for deploying Dockerized apps to coa-service — your self-hosted personal container cloud

Readme

coa-service-sdk

SDK and CLI for deploying Dockerized apps to coa-service — your self-hosted personal container cloud.

npm version npm downloads CI License: MIT

Full Documentation →


What is coa-service?

coa-service is a self-hosted mini-PaaS that runs on any single Linux server with Docker. It maintains a warm pool of containers and lets you deploy any Dockerized app to one via a REST API or this SDK — similar to Railway or Render, but running on your own machine.

┌─────────────────────────────────────────────────────────────────┐
│                      coa-service (your server)                  │
│                                                                 │
│  Pool of 5 containers, always ready:                            │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  ...    │
│  │ slot 0   │  │ slot 1   │  │ slot 2   │  │ slot 3   │         │
│  │ ssh:2201 │  │ ssh:2202 │  │ ssh:2203 │  │ ssh:2204 │         │
│  │ app:8001 │  │ app:8002 │  │ app:8003 │  │ app:8004 │         │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘         │
└─────────────────────────────────────────────────────────────────┘
         ▲ coa-service-sdk uploads your project tarball,
           builds it, and deploys it to a slot

Prerequisites

  • A running coa-service instance (set up guide)
  • Your API_KEY from the server's .env
  • Node.js 18+ (uses native fetch and FormData)
  • tar and docker available on the server (already there if coa-service is running)

Installation

# As a project dependency (SDK usage)
npm install coa-service-sdk

# Globally (CLI usage)
npm install -g coa-service-sdk

Or use without installing via npx:

npx coa-service-sdk deploy

Quick Start

1. Create coa.config.json in your project

{
  "baseUrl": "http://your-server-ip:3000",
  "apiKey":  "your-api-key",
  "containerPort": 3000
}

2. Make sure your project has a Dockerfile

# Example for a Node.js app
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

3. Deploy

npx coa-deploy deploy

Output:

→ Allocating container slot…
→ Slot allocated (port 2203). Building and deploying…
→ Project tarball: 42 KB
→ docker-compose.yml detected — using port 3000, env keys: [NODE_ENV]

✓ Deployed!
  App URL:  http://your-server-ip:8003
  SSH:      ssh -p 2203 sandbox@your-server-ip
  ID:       d3317ca7-0788-42ec-a843-8a628e803e34

CLI Reference

coa-deploy <command> [options]

| Command | Description | |---------|-------------| | deploy | Allocate a slot + build + deploy (default) | | allocate | Allocate a container slot without deploying | | release <id> | Return a container to the pool | | status | Show pool availability |

deploy options

| Flag | Description | Default | |------|-------------|---------| | --dir <path> | Project directory (must contain Dockerfile) | process.cwd() | | --port <n> | Port your app listens on inside the container | 3000 | | --container <id> | Deploy to an existing allocated container | (allocates a new one) | | --env KEY=val | Inject env var (repeatable) | — | | --name <label> | Label this allocation | — |

Examples

# Deploy current directory
npx coa-deploy deploy

# Deploy a specific directory with env vars
npx coa-deploy deploy --dir ./apps/api --port 8080 --env NODE_ENV=production --env DB_URL=postgres://...

# Re-deploy to the same container (hot-swap)
npx coa-deploy deploy --container d3317ca7-0788-42ec-a843-8a628e803e34

# Allocate a slot (SSH only, no deployment)
npx coa-deploy allocate --name my-project

# Check pool availability
npx coa-deploy status

# Release a container
npx coa-deploy release d3317ca7-0788-42ec-a843-8a628e803e34

Config via environment variables

If you don't want a coa.config.json, set these instead:

export COA_BASE_URL=http://your-server-ip:3000
export COA_API_KEY=your-api-key
npx coa-deploy deploy

SDK Reference

new CoaClient(options)

import { CoaClient } from 'coa-service-sdk';

const coa = new CoaClient({
  baseUrl: 'http://your-server-ip:3000',  // coa-service URL
  apiKey:  'your-api-key',
});

coa.deployNew(options) — allocate + build + deploy in one call

const result = await coa.deployNew({
  projectDir:    './my-app',      // directory with Dockerfile (default: process.cwd())
  containerPort: 3000,            // port app listens on inside container
  env: {
    NODE_ENV: 'production',
    DATABASE_URL: 'postgres://...',
  },
  assignedTo:  'clawdbot',        // optional label
  sshPublicKey: '...',            // optional; reads ~/.ssh/id_ed25519.pub by default
});

console.log(result.url);          // http://your-server:8003
console.log(result.sshCommand);   // ssh -p 2203 sandbox@your-server
console.log(result.containerId);  // d3317ca7-... (save this for re-deploys)

coa.deploy(options) — deploy to an existing container

Use this for updates — rebuilds the image in-place (hot-swap, zero downtime:

await coa.deploy({
  containerId:   'd3317ca7-0788-42ec-a843-8a628e803e34',
  projectDir:    '.',
  containerPort: 3000,
  env: { VERSION: '2.1.0' },
});

coa.allocate(options) — get SSH access only

const slot = await coa.allocate({
  assignedTo:  'my-app',
  sshPublicKey: '...',  // optional; reads ~/.ssh/id_ed25519.pub by default
});

console.log(slot.sshCommand);  // ssh -p 2203 sandbox@your-server
console.log(slot.sshPort);     // 2203
console.log(slot.containerId); // d3317ca7-...

coa.release(containerId) — return container to pool

await coa.release('d3317ca7-0788-42ec-a843-8a628e803e34');
// → Service reverted to sandbox image, slot marked AVAILABLE

coa.getPoolStatus() — check availability

const status = await coa.getPoolStatus();
console.log(status);
// { total: 5, available: 3, inUse: 2, error: 0 }

Managed Databases (Aurora-style PostgreSQL)

The SDK provides a databases sub-client for managing PostgreSQL instances — similar to AWS Aurora or Railway Databases.

coa.databases.create({ name }) — provision a new database

const db = await coa.databases.create({ name: 'my-app-db' });
console.log(db.connectionString);
// postgresql://coa:SECRET_PASSWORD@your-server:6001/my_app_db

// ⚠ Save these credentials — the password is only shown once!
console.log(db.dbPassword);  // clear-text password
console.log(db.host);        // your-server
console.log(db.port);        // 6001
console.log(db.dbName);      // my_app_db
console.log(db.dbUser);      // coa

You can also provide custom credentials:

const db = await coa.databases.create({
  name:       'my-app-db',
  dbName:     'custom_db_name',
  dbUser:     'custom_user',
  dbPassword: 'custom_password',
});

coa.databases.list() — list all databases

const dbs = await coa.databases.list();
for (const db of dbs) {
  console.log(`${db.name} — ${db.status} — ${db.host}:${db.port}`);
}

coa.databases.get(id) — get database details

const db = await coa.databases.get('uuid-…');
console.log(db.status); // 'AVAILABLE'
console.log(db.health); // 'running'

coa.databases.stop(id) / start(id) — pause & resume

await coa.databases.stop('uuid-…');   // data preserved
await coa.databases.start('uuid-…');  // back online

coa.databases.delete(id) — permanently delete

await coa.databases.delete('uuid-…');
// ⚠ Database, container, and all data are destroyed

coa.databases.importSql({ databaseId, filePath }) — import a .sql file

const result = await coa.databases.importSql({
  databaseId: 'uuid-…',
  filePath: './schema.sql',
});
console.log(result.statementsRun); // 42
console.log(result.durationMs);   // 120

coa.databases.getSchema(id) — introspect schema & get ERD

const schema = await coa.databases.getSchema('uuid-…');

// List tables
for (const table of schema.tables) {
  console.log(`${table.name} (${table.columns.length} columns, ~${table.rowEstimate} rows)`);
  for (const col of table.columns) {
    const flags = [col.isPrimaryKey && 'PK', col.isForeignKey && 'FK', col.isUnique && 'UQ'].filter(Boolean);
    console.log(`  ${col.name} ${col.dataType} ${flags.join(' ')}`);
  }
}

// Get the Mermaid ERD source (render with mermaid.js or paste into any Mermaid viewer)
console.log(schema.mermaidERD);

// Save ERD to a file
import fs from 'fs';
fs.writeFileSync('erd.mmd', schema.mermaidERD);

coa.databases.getConnectionInfo(id) — ORM connection snippets

const info = await coa.databases.getConnectionInfo('uuid-…');
console.log(info.prisma);     // datasource db { provider = "postgresql" … }
console.log(info.sequelize);  // new Sequelize({ … })
console.log(info.typeorm);    // new DataSource({ type: 'postgres', … })
console.log(info.drizzle);    // drizzle(postgres('…'))
console.log(info.pg);         // new Pool({ … })
console.log(info.knex);       // knex({ client: 'pg', … })
console.log(info.python);     // psycopg2.connect(…)
console.log(info.env);        // DATABASE_URL="postgresql://…"

coa.databases.getMetrics(id) — live database metrics

const m = await coa.databases.getMetrics('uuid-…');
console.log(m.sizeHuman);          // "12.4 MB"
console.log(m.connections.active);  // 3
console.log(m.maxConnections);      // 100
console.log(m.cacheHitRatio);       // "99.87%"
console.log(m.uptime);             // "2d 14h 32m"

coa.databases.listTables(id) — SQL Explorer

const tables = await coa.databases.listTables('uuid-…');
tables.forEach(t => console.log(`${t.schema}.${t.table} — ${t.columns} cols, ~${t.estimatedRows} rows`));

coa.databases.getTableDetail(id, table, schema?) — table detail

const detail = await coa.databases.getTableDetail('uuid-…', 'users');
detail.columns.forEach(c => console.log(`${c.name} ${c.type} ${c.isPrimaryKey ? 'PK' : ''}`));
console.log(detail.sizeTotal);   // "24 kB"
console.log(detail.indexes);     // [{ name, unique, columns, definition }]
console.log(detail.foreignKeys); // [{ name, column, foreignTable, foreignColumn }]

coa.databases.query(id, sql) — execute SQL

const result = await coa.databases.query('uuid-…', 'SELECT * FROM users LIMIT 10');
console.log(result.columns);    // ['id', 'email', 'name']
console.log(result.rows);       // [{ id: 1, email: '…' }, …]
console.log(result.rowCount);   // 10
console.log(result.durationMs); // 12

Managed Apps (Container-as-a-Service)

The apps sub-client lets you deploy and manage long-running application containers from any Docker image — no Dockerfile build step needed. Think of it as a lightweight Fly.io or Railway, running on your own server.

coa.apps.create(opts) — deploy a new app

const app = await coa.apps.create({
  name:     'my-bot',
  image:    'ghcr.io/my-org/discord-bot:latest',
  env:      { BOT_TOKEN: 'abc123', NODE_ENV: 'production' },
  ports:    { '3000': 'http' },
  memoryMb: 1024,
  cpus:     2,
});

// Wait for it to start
const ready = await coa.apps.waitUntilReady(app.id);
console.log(ready.url); // http://your-server:9001

coa.apps.list() — list all apps

const apps = await coa.apps.list();
for (const app of apps) {
  console.log(`${app.name} — ${app.status} — ${app.url}`);
}

coa.apps.get(id) — get app details

const app = await coa.apps.get('uuid-…');
console.log(app.status);      // 'RUNNING'
console.log(app.url);         // 'http://your-server:9001'
console.log(app.containerId); // Docker container ID

coa.apps.stop(id) / start(id) / restart(id)

await coa.apps.stop('uuid-…');    // pause — config preserved
await coa.apps.start('uuid-…');   // resume
await coa.apps.restart('uuid-…'); // stop + start

coa.apps.update(id, opts) — update env / image

// Merge new env vars
await coa.apps.update('uuid-…', {
  env: { MODEL: 'gpt-5-2', OPENAI_API_KEY: 'sk-new-key' },
});

// Change Docker image → triggers container recreation
await coa.apps.update('uuid-…', { image: 'my-app:v2' });

coa.apps.delete(id) — permanently destroy

await coa.apps.delete('uuid-…');
// ⚠ Container and all data destroyed

coa.apps.getLogs(id, tail?) — container logs

const { logs } = await coa.apps.getLogs('uuid-…', 500);
console.log(logs);

coa.apps.waitUntilReady(id, timeoutMs?) — poll until running

Useful after create() — polls every 2s until the app is RUNNING or times out.

const app = await coa.apps.create({ name: 'bot', image: 'node:20-slim' });
const ready = await coa.apps.waitUntilReady(app.id, 120_000);
console.log(ready.status); // 'RUNNING'

Full Database Workflow Example

import { CoaClient } from 'coa-service-sdk';
import fs from 'fs';

const coa = new CoaClient({
  baseUrl: process.env.COA_BASE_URL!,
  apiKey:  process.env.COA_API_KEY!,
});

async function setupDatabase() {
  // 1. Create a database
  const db = await coa.databases.create({ name: 'myapp-prod' });
  console.log(`✓ Database created: ${db.connectionString}`);
  console.log(`  Save this password: ${db.dbPassword}`);

  // 2. Import schema
  const importResult = await coa.databases.importSql({
    databaseId: db.id,
    filePath: './migrations/schema.sql',
  });
  console.log(`✓ Schema imported: ${importResult.statementsRun} statements in ${importResult.durationMs}ms`);

  // 3. View ERD
  const schema = await coa.databases.getSchema(db.id);
  console.log(`✓ ${schema.tables.length} tables found`);
  fs.writeFileSync('erd.mmd', schema.mermaidERD);
  console.log('✓ ERD saved to erd.mmd');

  // 4. Deploy an app that uses this database
  const app = await coa.deployNew({
    projectDir: '.',
    env: { DATABASE_URL: db.connectionString },
  });
  console.log(`✓ App deployed: ${app.url}`);
}

setupDatabase().catch(console.error);

docker-compose.yml support

The SDK auto-reads docker-compose.yml (if present) and extracts port + env vars as defaults — so if your project already has one, you don't need to specify anything extra:

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"          # ← SDK reads containerPort=3000 from here
    environment:
      - NODE_ENV=production   # ← merged as default env vars
      - APP_NAME=ClawdBot

Then just run:

npx coa-deploy deploy  # port + env read automatically from compose file

Port Layout

Each slot has a fixed SSH port and app port on the host:

| Slot | SSH Port | App Port | |------|----------|----------| | 0 | 2201 | 8001 | | 1 | 2202 | 8002 | | 2 | 2203 | 8003 | | 3 | 2204 | 8004 | | 4 | 2205 | 8005 |


TypeScript Types

import type {
  CoaClientOptions,
  AllocateOptions,
  AllocateResult,
  DeployOptions,
  DeployNewOptions,
  DeployResult,
  PoolStatus,
  // Databases
  DatabaseInfo,
  DatabaseCreatedResult,
  CreateDatabaseOptions,
  ImportSqlOptions,
  ImportSqlResult,
  // Schema / ERD
  SchemaResult,
  TableInfo,
  ColumnInfo,
  ForeignKeyInfo,
  DatabaseStatus,
  // Connection Info & Metrics
  ConnectionInfo,
  DatabaseMetrics,
  DatabaseConnectionStats,
  DatabaseStats,
  // SQL Explorer
  TableListItem,
  TableDetail,
  TableDetailColumn,
  TableDetailIndex,
  TableDetailForeignKey,
  QueryResult,
  // Managed Apps
  AppInfo,
  AppLogs,
  AppStatus,
  CreateAppOptions,
  UpdateAppOptions,
} from 'coa-service-sdk';

Full Workflow Example (Programmatic)

import { CoaClient } from 'coa-service-sdk';

const coa = new CoaClient({
  baseUrl: process.env.COA_BASE_URL!,
  apiKey:  process.env.COA_API_KEY!,
});

async function deployClawdBot() {
  // 1. Check pool before deploying
  const pool = await coa.getPoolStatus();
  if (pool.available === 0) throw new Error('Pool exhausted — try later');

  // 2. Deploy (allocate + build + run)
  const deploy = await coa.deployNew({
    projectDir:    '.',
    containerPort: 3000,
    assignedTo:    'clawdbot-prod',
    env: {
      NODE_ENV:   'production',
      BOT_TOKEN:  process.env.BOT_TOKEN!,
    },
  });

  console.log(`✓ ClawdBot live at ${deploy.url}`);
  console.log(`  SSH: ${deploy.sshCommand}`);

  // 3. Save containerId for future re-deploys
  return deploy.containerId;
}

deployClawdBot().catch(console.error);

Contributing

Issues and PRs welcome at github.com/hemaal/coa-service-sdk.

License

MIT © Hemaal

Install

npm install coa-service-sdk
# or
npx coa-service-sdk deploy

Quick Start

Option 1 — Programmatic (Node.js / TypeScript)

import { CoaClient } from 'coa-service-sdk';

const coa = new CoaClient({
  baseUrl: 'http://192.168.0.127:3000',  // your coa-service URL
  apiKey:  'your-api-key',
});

// One-liner: allocate a slot + build + deploy
const result = await coa.deployNew({
  projectDir:    './my-app',      // must contain a Dockerfile
  containerPort: 3000,            // port your app listens on inside container
  env: { NODE_ENV: 'production' },
  assignedTo: 'clawdbot',
});

console.log(`✓ Live at: ${result.url}`);
// → ✓ Live at: http://192.168.0.127:8003

// Later — release the slot back to the pool
await coa.release(result.containerId);

Option 2 — CLI (coa-deploy)

Create coa.config.json in your project root:

{
  "baseUrl": "http://192.168.0.127:3000",
  "apiKey":  "your-api-key",
  "containerPort": 3000
}

Then from your project directory:

# Allocate + build + deploy (one command)
npx coa-deploy deploy

# Or deploy to a specific already-allocated container
npx coa-deploy deploy --container <containerId>

# With env vars
npx coa-deploy deploy --env NODE_ENV=production --env DATABASE_URL=postgres://...

# Check pool availability
npx coa-deploy status

# Release a container back to pool
npx coa-deploy release <containerId>

docker-compose.yml support

If your project has a docker-compose.yml, the SDK automatically reads:

  • ports: ["HOST:CONTAINER"] → uses CONTAINER as containerPort
  • environment: → merged as default env vars (explicit env option overrides)
# docker-compose.yml in your project
services:
  app:
    build: .
    ports:
      - "3000:3000"       # ← SDK reads containerPort=3000 from here
    environment:
      - NODE_ENV=production
      - APP_NAME=ClawdBot

Deploy Flow

your project/               coa-service (host)
    Dockerfile         →    docker build -t coa-app-slot2:latest
    docker-compose.yml →    port/env extraction
                       →    service update (new image + port mapping)
                       →    http://192.168.0.127:8003  ← your app

Port Layout

| Slot | SSH Port | App Port | |------|----------|----------| | 0 | 2201 | 8001 | | 1 | 2202 | 8002 | | 2 | 2203 | 8003 | | 3 | 2204 | 8004 | | 4 | 2205 | 8005 |

Re-deploy (update your app)

Just call deploy() again with the same containerId — the server rebuilds the image and hot-swaps the Swarm service:

await coa.deploy({
  containerId: 'existing-container-id',
  projectDir:  '.',
  env: { VERSION: '2.0' },
});

Release

When you're done, release the container back to the pool so others can use it:

await coa.release(containerId);
// → Service reverted to sandbox image, slot marked AVAILABLE