How Openship's Multi-Runtime Architecture Works: DockerRuntime vs BareRuntime vs CloudRuntime
Openship unifies build and deployment operations behind a common RuntimeAdapter interface, enabling workloads to run locally via Docker, directly on bare-metal hosts, or in Oblien's managed cloud through three specialized adapters that declare environment-specific capabilities.
Openship's multi-runtime architecture allows developers to deploy applications across different infrastructure types without changing application code. The oblien/openship repository implements this abstraction through a capability-driven adapter pattern defined in packages/adapters/src/runtime/types.ts. By isolating runtime-specific logic behind a uniform interface, the platform supports seamless execution whether targeting a local Docker socket, a remote VPS, or Oblien's cloud workspaces.
The RuntimeAdapter Interface Foundation
The architecture centers on the RuntimeAdapter interface defined in packages/adapters/src/runtime/types.ts. This contract standardizes lifecycle operations including build, deploy, stop, and streamLogs across all execution environments.
Capability-Driven Feature Gates
Each runtime declares specific capabilities that determine available functionality. For example, DockerRuntime supports multiServiceDeploy and serviceShell, while BareRuntime manages only single processes without multi-service composition. The UI queries runtime.supports(capability) before enabling features, preventing runtime-specific errors at the interface level.
The Three Runtime Implementations
Openship provides three concrete adapters in packages/adapters/src/runtime/, each optimized for distinct infrastructure patterns.
DockerRuntime: Container-Native Execution
The DockerRuntime class in packages/adapters/src/runtime/docker.ts executes builds and deployments against a Docker Engine. It handles:
- Building images via
docker buildstreamed through local sockets, TCP, or SSH-tunneled connections - Managing container lifecycles including start, stop, restart, and destroy operations
- Supporting multi-service Docker Compose-like deployments through
multiServiceDeploy
BareRuntime: Direct Host Execution
The BareRuntime class in packages/adapters/src/runtime/bare.ts operates directly on host machines via local processes or SSH:
- Cloning and building source code on the target host using a
CommandExecutor - Deploying processes supervised by systemd or nohup
- Managing releases through hard-link deduplication and atomic release directories
CloudRuntime: Managed Cloud Workspaces
The CloudRuntime class in packages/adapters/src/runtime/cloud.ts provisions temporary workspaces in Oblien's cloud infrastructure:
- Provisioning isolated workspaces for running the full
runBuildPipeline - Deploying as cloud-managed workloads with automatic port exposure and custom domain support
- Handling Dockerfile-based builds within ephemeral cloud environments
Runtime Selection and Factory Pattern
Runtime mode is determined by project configuration and instantiated through a centralized factory that enables lazy loading.
Configuration Schema
The runtime mode is defined in packages/core/src/openship-config/schema.ts via the runtime?: OpenshipRuntime field. Allowed values are "bare" or "docker". Cloud mode activates automatically when DeployConfig.target === "cloud".
Lazy Loading via createRuntime
The factory function createRuntime in packages/adapters/src/runtime/index.ts implements lazy loading to minimize bundle size by importing only the required runtime code:
// packages/adapters/src/runtime/index.ts
export async function createRuntime(opts: CreateRuntimeOptions): Promise<RuntimeAdapter> {
if (opts.mode === "docker") {
const { DockerRuntime } = await import("./docker");
return await DockerRuntime.create(opts.docker, opts.systemManager);
}
if (opts.mode === "bare") {
const { BareRuntime } = await import("./bare");
return new BareRuntime(opts.bare);
}
// Cloud mode (used only when DeployConfig.target === "cloud")
const { CloudRuntime } = await import("./cloud");
return new CloudRuntime(opts.client, opts.adminProxy);
}
The caller passes concrete connection options—such as Docker socket paths, SSH executors, or Oblien API clients—and receives a RuntimeAdapter that can be used uniformly regardless of the underlying environment.
Execution Flow Comparison
Each runtime implements distinct strategies for building and deploying applications, optimized for their specific environments.
Build Strategies
- DockerRuntime: Streams build contexts to the Docker daemon via
DockerRuntime.build(), using eitherbuildViaDockerodeor fast SSH-tar pipes for remote engines - BareRuntime: Either builds server-side via
buildOnTargetusing the suppliedCommandExecutor, or builds locally then transfers files to the target host - CloudRuntime: Provisions a workspace via
CloudRuntime.build()and executes the sharedrunBuildPipelineinside it, or uploads pre-built bundles to the cloud environment
Deployment Strategies
- DockerRuntime: Creates containers from built images, forwards ports, and manages container state via
DockerRuntime.deploy() - BareRuntime: Uses
ProcessSupervisorto start processes, writes environment variables to release directories, and manages atomic deployments throughmakeActive,archive, andpurgeoperations - CloudRuntime: Makes workspaces permanent, connects custom domains via
publicEndpoints, and registers workloads with Oblien's routing layer
Practical Implementation Examples
Initializing a Runtime
import { createRuntime } from "@repo/adapters/runtime";
const runtime = await createRuntime({
mode: "docker", // "docker" | "bare" | "cloud"
docker: { socketPath: "/var/run/docker.sock" },
systemManager: featureManager,
});
Building with DockerRuntime
await runtime.build({
projectId: "proj123",
sessionId: "sess456",
repoUrl: "https://github.com/example/app",
branch: "main",
stack: "node",
});
Deploying with BareRuntime
await runtime.deploy({
deploymentId: "dep789",
projectId: "proj123",
port: 3000,
envVars: { NODE_ENV: "production" },
startCommand: "npm start",
});
Cloud Deployment with Custom Domain
await runtime.deploy({
deploymentId: "dep999",
projectId: "proj123",
port: 8080,
publicEndpoints: [{
domain: "myapp",
domainType: "custom",
customDomain: "app.example.com"
}],
envVars: { NODE_ENV: "production" },
});
Summary
- Openship's multi-runtime architecture relies on the
RuntimeAdapterinterface inpackages/adapters/src/runtime/types.tsto unify Docker, bare-metal, and cloud execution behind a single contract. DockerRuntimemanages container lifecycles and multi-service deployments via Docker Engine APIs inpackages/adapters/src/runtime/docker.ts.BareRuntimeexecutes processes directly on hosts using systemd or nohup supervision with release directory management inpackages/adapters/src/runtime/bare.ts.CloudRuntimeprovisions Oblien cloud workspaces and registers workloads with managed routing layers inpackages/adapters/src/runtime/cloud.ts.- The
createRuntimefactory inpackages/adapters/src/runtime/index.tsenables lazy loading and runtime selection based on theruntimeconfiguration field. - Capability detection prevents unsupported operations by checking
runtime.supports()before execution, ensuring UI elements align with runtime functionality.
Frequently Asked Questions
How does Openship decide which runtime to use?
Runtime selection depends on the runtime field in packages/core/src/openship-config/schema.ts, which accepts "bare" or "docker". Cloud mode activates when DeployConfig.target === "cloud". The createRuntime factory in packages/adapters/src/runtime/index.ts instantiates the appropriate adapter based on this mode, passing connection options like Docker sockets, SSH executors, or Oblien API clients to the constructor.
Can I switch between runtimes without modifying my application code?
Yes. Because all runtimes implement the same RuntimeAdapter interface defined in packages/adapters/src/runtime/types.ts, applications remain agnostic to the execution environment. The platform handles runtime-specific concerns like containerization versus process supervision internally, allowing you to migrate from Docker to bare-metal or cloud by changing configuration rather than application code.
What happens if a runtime doesn't support a specific feature?
Each runtime declares its capabilities (such as multiServiceDeploy or serviceShell) through the supports() method. The UI queries runtime.supports(capability) before enabling features. For example, because BareRuntime lacks multiServiceDeploy, the interface disables multi-service deployment options when targeting bare-metal hosts, preventing runtime-specific errors before they occur.
Where are the runtime implementations located in the source code?
The three adapters reside in packages/adapters/src/runtime/: docker.ts contains DockerRuntime, bare.ts contains BareRuntime, and cloud.ts contains CloudRuntime. The shared interface definitions are in types.ts, while the factory function is exported from index.ts. Configuration validation for the runtime field is defined in packages/core/src/openship-config/schema.ts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →