Getting started
This guide creates one application graph for local development and Kubernetes delivery.
Requirements
- Node.js 24 or newer
- a project with at least one package-level
devscript - Portless for
tsops dev - Docker for image builds
kubectlwith a configured context for plan and deploy
Portless, Docker, and kubectl are only required for the commands that use them.
Install
pnpm add -D tsops portlessThe tsops repository uses pnpm, but local application commands may use Bun, pnpm, Yarn, or npm. tsops dev detects the nearest lockfile for each application.
Create the application graph
Add tsops.config.ts at the repository root:
import { defineConfig } from 'tsops'
const config = defineConfig({
project: 'orchard',
namespaces: {
local: {
runtime: 'local',
domain: 'orchard.localhost'
},
production: {
runtime: 'kubernetes',
domain: 'orchard.example.com'
}
},
clusters: {
production: {
apiServer: 'https://kubernetes.example.com:6443',
context: 'production',
namespaces: ['production']
}
},
images: {
registry: 'ghcr.io/acme/orchard',
tagStrategy: 'git-sha'
},
apps: {
api: {
build: {
type: 'dockerfile',
context: 'apps/api',
dockerfile: 'apps/api/Dockerfile',
inputs: ['apps/api/**', 'packages/shared/**', 'pnpm-lock.yaml'],
sourceKey: true,
cache: { type: 'registry', mode: 'max' }
},
ports: [{ name: 'http', port: 80, targetPort: 3000 }]
},
web: {
build: {
type: 'dockerfile',
context: 'apps/web',
dockerfile: 'apps/web/Dockerfile'
},
needs: ['api'],
ingress: ({ domain }) => ({ domain }),
ports: [{ name: 'http', port: 80, targetPort: 3000 }]
}
}
})
export default configEach build context should contain a package.json with a dev script. For unusual layouts, declare the command explicitly:
apps: {
worker: {
dev: ['bun', 'run', 'worker'],
// ...image or build configuration
}
}Use the graph from application code
Import the config from TypeScript application code and ask for a semantic endpoint:
import config from '../../tsops.config.js'
const apiUrl = config.url('api', 'service')
const response = await fetch(`${apiUrl}/health`)Under tsops dev, the helper consumes TSOPS_DEV_URLS and returns the Portless URL. In Kubernetes it returns the Service URL for the selected namespace. Application code does not need separate local and cluster host-name constants.
Run locally
pnpm tsops devBecause the config contains exactly one namespace with runtime: 'local', tsops selects it automatically. It discovers each package's dev script and prints stable routes such as:
https://api.orchard.localhost
https://web.orchard.localhostUse a subset when needed:
pnpm tsops dev --app apiSee Local development for explicit commands, worktree behavior, and URL discovery.
Review a cluster rollout
Start without external side effects:
pnpm tsops plan --namespace production --dry-runThen run a real cluster diff:
pnpm tsops plan --namespace productionThe plan groups namespace-level resources, application resources, validation failures, and managed orphans. Treat this output as the required review step before deploy.
Build images
Authenticate to the configured registry, then run:
pnpm tsops build --namespace production --source-key--source-key hashes selected source files and build metadata. If the resulting image already exists, tsops skips the Docker build and resolves its immutable digest. Registry-backed BuildKit cache remains available for builds that are not exact matches.
For a monorepo diff, combine it with filtering:
pnpm tsops build --namespace production --filter origin/main --source-keyDeploy
pnpm tsops deploy --namespace productionCI can separate build and deploy by writing digest references to a JSON file:
{
"api": "ghcr.io/acme/orchard/api@sha256:abcd...",
"web": "ghcr.io/acme/orchard/web@sha256:ef01..."
}pnpm tsops deploy --namespace production --image-digests @images.jsonMutable tags and unknown application names are rejected before resources are applied.
Add pull-request previews
A preview is an overlay namespace that inherits from a stable base namespace. It can deploy only affected applications while routing the rest back to the base through generated ExternalName Services.
pnpm tsops up preview --var pr=42 --apps-from-changes --base-ref origin/main
pnpm tsops down preview --var pr=42Continue with the Preview environments contract and the runnable examples/preview-namespaces configuration.