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 build streamed 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 either buildViaDockerode or fast SSH-tar pipes for remote engines
  • BareRuntime: Either builds server-side via buildOnTarget using the supplied CommandExecutor, or builds locally then transfers files to the target host
  • CloudRuntime: Provisions a workspace via CloudRuntime.build() and executes the shared runBuildPipeline inside 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 ProcessSupervisor to start processes, writes environment variables to release directories, and manages atomic deployments through makeActive, archive, and purge operations
  • 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

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →