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/:
| File | Responsibility |
|---|---|
runtime.go | Runtime interface definition |
detector.go | Auto-detection logic — tries runtimes in order |
go.go | Go runtime adapter |
python.go | Python runtime adapter |
node.go | Node.js runtime adapter |
bun.go | Bun runtime adapter |
deno.go | Deno runtime adapter |
ruby.go | Ruby runtime adapter |
php.go | PHP 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:
| Runtime | Detection Heuristics |
|---|---|
| Go | Entrypoint ends in .go, or is a directory containing .go files, or go.mod exists |
| Python | Entrypoint ends in .py, or requirements.txt / setup.py / pyproject.toml exists |
| Node.js | Entrypoint ends in .js/.mjs/.ts, or package.json exists |
| Bun | Entrypoint ends in .ts/.bun, or bun.lockb / bunfig.toml exists |
| Deno | deno.json or deno.jsonc exists in the working directory |
| Ruby | Gemfile or Gemfile.lock exists in the working directory |
| PHP | composer.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:
- Checks for
go.modin the working directory - If the entrypoint is a
.gofile or directory, usesgo run <entrypoint> - If the entrypoint is a compiled binary, executes it directly
- Sets
GOCACHEandGOMODCACHEfrom 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:
- Resolves the Python binary — checks
python3first, falls back topython - If
VIRTUAL_ENVis set in config env, prepends the venv'sbin/toPATH - 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:
- Checks for
package.json— if it exists and has a matching script, can usenpm run - Otherwise executes
node server.js --port 3000 - Supports
.js,.mjs,.tsentrypoints - For TypeScript, checks for
tsx,ts-node, or falls back tonode --loader
Command: node server.js --port 3000
Bun Runtime
processes:
api:
runtime: bun
entrypoint: src/index.ts
args: ["--dev"]
The Bun runtime:
- Uses
bun run src/index.ts --dev - Bun natively supports TypeScript, so no additional transpilation is needed
- Checks for
bun.lockbas 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:
- Detects projects via
deno.jsonordeno.jsonc - Builds command as
deno run [args...] <entrypoint> - 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:
- Detects projects via
GemfileorGemfile.lock - Resolves Ruby binary via PATH (
ruby3first, falls back toruby) - With
use_bundle: true, wraps execution withbundle exec ruby <entrypoint> - Supports custom interpreter via the
interpreterfield
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:
- Detects projects via
composer.json,artisan(Laravel), or.phpfiles - Resolves PHP binary via PATH, falls back to
php - Supports custom interpreter via the
interpreterfield
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
- Architecture Overview — How runtimes fit into the system
- Configuration: Runtime — Full runtime config options
- Developer Guide: Adding Runtimes — How to add a new runtime adapter