@rpcbase/services
v0.12.0
Published
Tenant services are long-running Docker containers managed by rpcbase. Each tenant gets one services container that runs the app-owned services entrypoint.
Readme
@rpcbase/services
Tenant services are long-running Docker containers managed by rpcbase. Each tenant gets one services container that runs the app-owned services entrypoint.
Apps define their services runtime in:
src/services/
Dockerfile
index.ts
service-1/
service-2/The app decides how index.ts starts and supervises its internal services. rpcbase only builds or runs one container per tenant and injects tenant context.
import { startTenantServices } from "@rpcbase/services"
await startTenantServices({
configs: [
{
siteId: "rb-app",
tenantId: "00000000",
build: {
context: "./sample-app",
dockerfile: "src/services/Dockerfile",
platform: "linux/amd64",
src: ["package.json", "package-lock.json", "src/services"],
},
egress: {
mode: "tailscaleExitNode",
exitNode: "home-exit-node",
},
},
],
pruneStale: true,
})Egress modes:
direct: the services container uses the configured Docker network.tailscaleExitNode: rpcbase starts one Tailscale sidecar for the tenant and the services container shares its network namespace.
build.src is optional. When set, only those paths are sent to the Docker daemon for the build context. When omitted, the whole build context is sent.
Data:
Each services container gets a /data bind mount by convention. The host path is:
<build.context>/infrastructure/data/rb-services-<tenantId>For example, tenant ca-roule stores service data under infrastructure/data/rb-services-ca-roule. Apps should write service-owned persistent state below /data inside the container. If a config already binds /data or a /data/... subpath, rpcbase leaves that bind untouched.
Tenant service RPC
Apps can expose short control commands from a tenant services process and call them from an API process without opening an HTTP port or using Redis. rpcbase runs a small CLI inside the tenant services container with Docker exec; that CLI forwards the request to the already-running service process through a private Unix socket.
Service entrypoint:
import {
isTenantServiceRpcCliCommand,
runTenantServiceRpcCliFromProcessArgs,
startTenantServiceRpcServer,
} from "@rpcbase/services"
if (isTenantServiceRpcCliCommand()) {
await runTenantServiceRpcCliFromProcessArgs()
} else {
const rpc = await startTenantServiceRpcServer()
rpc.register("service.command", async (payload) => {
return { payload }
})
}API side:
import { callTenantServiceRpc } from "@rpcbase/services"
const response = await callTenantServiceRpc({
projectName: "rb-services",
siteId: "rb-app",
tenantId: "00000000",
command: "service.command",
payload: { value: 1 },
})The RPC response is { ok: true, data?: unknown } or { ok: false, error: { message, code? } }. Payload validation belongs to the app command handler.
