Skip to main content

Runtime Adapters

Runix supports seven runtime adapters for executing processes. Each adapter implements the Runtime interface and knows how to build the correct exec.Cmd for its language.

Located at internal/runtime/:

FileResponsibility
runtime.goRuntime interface definition
detector.goAuto-detection logic — tries runtimes in order
go.goGo runtime adapter
python.goPython runtime adapter
node.goNode.js runtime adapter
bun.goBun runtime adapter
deno.goDeno runtime adapter
ruby.goRuby runtime adapter
php.goPHP runtime adapter

Runtime Interface​

type Runtime interface {
// Name returns the runtime identifier (e.g., "go", "python").
Name() string

// Detect checks if the runtime matches the given directory.
Detect(dir string) bool

// StartCmd creates the exec.Cmd to run the process.
StartCmd(opts StartOptions) (*exec.Cmd, error)
}

Detection Order​

When runtime is not specified in the config, Runix tries to detect the correct runtime:

Detection is based on file extension and entrypoint characteristics:

RuntimeDetection Heuristics
GoEntrypoint ends in .go, or is a directory containing .go files, or go.mod exists
PythonEntrypoint ends in .py, or requirements.txt / setup.py / pyproject.toml exists
Node.jsEntrypoint ends in .js/.mjs/.ts, or package.json exists
BunEntrypoint ends in .ts/.bun, or bun.lockb / bunfig.toml exists
Denodeno.json or deno.jsonc exists in the working directory
RubyGemfile or Gemfile.lock exists in the working directory
PHPcomposer.json or artisan exists, or any .php files in the working directory

Go Runtime​

processes:
api:
runtime: go
entrypoint: ./cmd/api
args: ["--port", "8080"]

The Go runtime:

  1. Checks for go.mod in the working directory
  2. If the entrypoint is a .go file or directory, uses go run <entrypoint>
  3. If the entrypoint is a compiled binary, executes it directly
  4. Sets GOCACHE and GOMODCACHE from environment if available

Command: go run ./cmd/api --port 8080

Python Runtime​

processes:
worker:
runtime: python
entrypoint: worker.py
args: ["--verbose"]
env:
VIRTUAL_ENV: .venv

The Python runtime:

  1. Resolves the Python binary — checks python3 first, falls back to python
  2. If VIRTUAL_ENV is set in config env, prepends the venv's bin/ to PATH
  3. Executes python3 worker.py --verbose

Command: python3 worker.py --verbose

Node.js Runtime​

processes:
web:
runtime: node
entrypoint: server.js
args: ["--port", "3000"]

The Node.js runtime:

  1. Checks for package.json — if it exists and has a matching script, can use npm run
  2. Otherwise executes node server.js --port 3000
  3. Supports .js, .mjs, .ts entrypoints
  4. For TypeScript, checks for tsx, ts-node, or falls back to node --loader

Command: node server.js --port 3000

Bun Runtime​

processes:
api:
runtime: bun
entrypoint: src/index.ts
args: ["--dev"]

The Bun runtime:

  1. Uses bun run src/index.ts --dev
  2. Bun natively supports TypeScript, so no additional transpilation is needed
  3. Checks for bun.lockb as a hint for Bun projects

Command: bun run src/index.ts --dev

Deno Runtime​

processes:
api:
runtime: deno
entrypoint: main.ts
args: ["--allow-net", "--allow-read"]

The Deno runtime:

  1. Detects projects via deno.json or deno.jsonc
  2. Builds command as deno run [args...] <entrypoint>
  3. Permission flags are passed through args

Command: deno run --allow-net --allow-read main.ts

Ruby Runtime​

processes:
worker:
runtime: ruby
entrypoint: worker.rb
use_bundle: true

The Ruby runtime:

  1. Detects projects via Gemfile or Gemfile.lock
  2. Resolves Ruby binary via PATH (ruby3 first, falls back to ruby)
  3. With use_bundle: true, wraps execution with bundle exec ruby <entrypoint>
  4. Supports custom interpreter via the interpreter field

Command (direct): ruby worker.rb Command (bundle): bundle exec ruby worker.rb

PHP Runtime​

processes:
app:
runtime: php
entrypoint: artisan
args: ["serve", "--port", "8000"]

The PHP runtime:

  1. Detects projects via composer.json, artisan (Laravel), or .php files
  2. Resolves PHP binary via PATH, falls back to php
  3. Supports custom interpreter via the interpreter field

Command: php artisan serve --port 8000

Explicit Runtime Selection​

Use the --runtime CLI flag or runtime config field to skip detection:

# Via CLI
runix start --name api --runtime go -- ./cmd/api

# Via config
processes:
api:
runtime: go
entrypoint: ./cmd/api

Valid values: go, python, node, bun, deno, ruby, php

Custom Entrypoints​

For non-standard setups, you can specify a shell command as the entrypoint:

processes:
custom:
runtime: python
entrypoint: gunicorn
args: ["myapp:app", "--bind", "0.0.0.0:8000", "--workers", "4"]

Working Directory​

Each process's working directory is set via cwd:

processes:
api:
entrypoint: ./cmd/api
cwd: /home/user/projects/myapp

If not specified, defaults to the current directory when runix start is executed.

What's Next​