Skip to content

MonkScript Reference

MonkScript is the YAML-based configuration language used by Monk to define templates.

Basic Syntax and Structure

MonkScript follows standard YAML syntax with some Monk-specific extensions. Each file defines a namespace and one or more entities that represent infrastructure components like applications, databases, or complete stacks.

File Layout

Every MonkScript file must begin with a namespace declaration, followed by entity definitions:

namespace: my-namespace
# Entity definitions follow
entity-one:
defines: runnable
# entity configuration...
entity-two:
defines: group
# entity configuration...

MonkScript files are usually named after the namespace they belong to and can be placed in any directory - no special project layout is required.

Naming Convention

All identifiers in MonkScript must use kebab-case (lowercase letters, numbers, and hyphens as separators):

# Correct (kebab-case)
my-entity:
defines: runnable
# Incorrect
MyEntity: # Don't use PascalCase
my_entity: # Don't use snake_case
myEntity: # Don't use camelCase

This naming convention applies to entity names, variable names, container names, service names, and other identifiers.

Namespace Paths

Namespace paths are used to reference entities within a namespace. They follow these rules:

  • Each path segment must be a kebab-case identifier (lowercase letters, numbers, hyphens)
  • Path segments are separated by forward slashes
  • Path can optionally start with either:
    • ./ to reference current namespace
    • / for absolute paths
  • Path cannot end with a slash
  • Single dot segments (like . or ./) are not allowed
namespace: my-namespace
my-entity:
defines: runnable

Path to the entity above is my-namespace/my-entity.

Paths can reference entities in other namespaces:

namespace: my-namespace
my-entity:
defines: runnable
inherits: other-namespace/my-entity

Entity Types

MonkScript supports four main entity types:

Runnable

A runnable is the basic unit of execution in Monk, defining one or more containers that run together in a single pod.

my-app:
defines: runnable
containers:
app:
image: nginx:latest
# Other sections

Group

A group collects multiple runnables and other entities that will be run together as a logical unit.

my-stack:
defines: group
members:
- my-namespace/my-app
- my-namespace/my-db
# Limited to variables, metadata, affinity, and permitted-secrets

Groups support either members (recommended) or the deprecated runnable-list property.

Entity Type Definition

An entity type definition creates a reusable template that can be instantiated. This is a powerful feature for creating your own abstractions.

my-entity:
defines: entity
schema:
# ...
# Define reusable components

Entity Instance

Creates an instance of a defined entity type.

my-instance:
defines: my-namespace/my-template
# Fields as defined in my-namespace/my-template

Deprecated: Fragments

Note: Fragments (configuration blocks without a defines property) are deprecated and should be avoided in new code.

Inheritance and Composition

The defines Property

The defines property specifies what type an entity is:

Built-in entity types:

  • defines: runnable - Creates a runnable
  • defines: group - Creates a group (previously process-group)
  • defines: entity - Creates an entity type definition
  • defines: process-group - Creates a process group (deprecated)

Custom entity types:

  • defines: my-namespace/my-entity - Creates an instance of an entity type

The inherits Property

The inherits property allows entities to inherit configuration from other entities:

my-app:
defines: runnable
inherits: my-namespace/common-config
# More specific configuration

When using inherits, configuration from the inherited entity is merged with the current entity:

  1. Simple values in the current entity override values from the inherited entity
  2. Lists are combined (with duplicates removed)
  3. Maps/objects are deeply merged, with current values taking precedence for duplicate keys
  4. This applies recursively to all nested configuration

Multiple inheritance is not supported - an entity can only inherit from one other entity.

Override Syntax

When inheriting, you can explicitly override inherited sections using the [override] syntax:

my-app:
defines: runnable
inherits: my-namespace/base-app
connections[override]:
# This completely replaces inherited connections
db:
target: my-namespace/new-database
service: postgres
depends[override]: null # This removes inherited dependencies

The [override] syntax is available for connections, depends, and affinity sections.

Entity Sections by Type

Different entity types support different sections:

Runnable Sections

Runnables support the most comprehensive set of sections:

  • containers (required) - Container definitions
  • variables - Entity variables
  • files - File definitions
  • connections - Connections to other entities
  • services - Service definitions
  • checks - Health/readiness checks
  • depends - Dependencies
  • recovery - Recovery configuration
  • affinity - Scheduling configuration
  • volumes - Persistent volume configuration
  • scale - Scaling configuration
  • metadata - Entity metadata
  • requirements - Resource requirements
  • actions - Custom actions
  • permitted-secrets - Secret access permissions
  • monk-ui - UI configuration
  • version - Entity version

Group Sections

Groups support:

  • members or runnable-list - List of entities to include
  • variables - Entity variables
  • metadata - Entity metadata
  • affinity - Scheduling configuration
  • permitted-secrets - Secret access permissions
  • monk-ui - UI configuration

Entity Type Definition Sections

Entity type definitions support:

  • schema - JSON Schema for validation
  • variables - Entity variables
  • services - Service definitions
  • connections - Connection definitions
  • metadata - Entity metadata
  • requires - Required JS modules
  • lifecycle - Lifecycle hooks

Entity Instance Sections

Entity instances support:

  • variables - Entity variables
  • metadata - Entity metadata
  • monk-ui - UI configuration
  • services - Service definitions
  • connections - Connection definitions
  • checks - Health/readiness checks
  • depends - Dependencies
  • Custom fields and sections defined in the entity type definition

Variables

Variables provide a way to parameterize your configuration and can be referenced throughout the entity:

variables:
port: 8080
database-url: "postgres://user:pass@host:5432/db"
debug-mode:
type: bool
value: true
description: "Enable debug logging"
env: DEBUG_MODE # Exposed as an environment variable
required: true # Must be set before running
optional-var:
value: null # Can be null
required: false # Optional variable

Variables can be simple values or complex objects with type information and descriptions.

Variable Types

Variables support these types:

  • string - Text values
  • int - Integer numbers
  • float - Floating-point numbers
  • bigint - Large integer values
  • bool - Boolean true/false values

Environment Variables

Variables can be exposed as environment variables using flexible naming patterns:

variables:
my-var:
type: string
value: "example"
env: MY_VAR # Standard uppercase
another-var:
env: foo_bar # Lowercase with underscores
value: "example"
script-var:
env: <- $dynamic_env_name # ArrowScript for dynamic names
value: <- 5 10 add

Containers

Containers define the actual execution units within a runnable (only valid in runnable entities):

containers:
web:
image: nginx:latest
image-tag: 1.21 # Can also use ArrowScript: <- $version
arch: amd64 # Target architecture
ports:
- "80:80" # [[ip:]host_port:]container_port[/protocol]
- "127.0.0.1:8080:80/tcp" # More specific format
paths:
- "/data:/app/data" # source:target[:mode,options]
- "/host/config:/app/config:ro"
environment:
- "DEBUG=true" # VAR=value format
- "LOG_LEVEL=info"
bash: "nginx -g 'daemon off;'" # Command to run
cmd: "/usr/sbin/nginx" # Override CMD from Dockerfile
entrypoint: "/docker-entrypoint.sh" # Container entrypoint
workdir: "/app" # Working directory
user: 1000 # User ID or name
restart: "unless-stopped" # Restart policy
stop-signal: "SIGTERM" # Stop signal
stop-grace-period: "30s" # Grace period before SIGKILL
# Container lifecycle hooks
hooks:
container-started: container-started-action # refers to an action defined in the entity
container-stopped: container-stopped-action
container-starting: container-starting-action
container-stopping: container-stopping-action
# Secret mounting
mounted-secrets:
api-key:
path: "/var/secrets/api-key"
unmount-on: "ready" # ready, purge, stop, timeout
timeout: "5m"
# Container labels
labels:
- "com.example.service=web"
- "version=1.0"
# Security settings
privileged: false
cap-add:
- "NET_ADMIN"
- "SYS_TIME"
cap-drop:
- "ALL"
# Resource limits
ulimits:
nofile:
soft: 1024
hard: 2048
cpuquota: 50000 # CPU quota in microseconds
memory: 536870912 # Memory limit in bytes
shm-size: 67108864 # Shared memory size in bytes
# Logging configuration
logs:
quota: "100M" # Maximum log file size
count: 5 # Number of log files to keep
# Build configuration (internal use)
dockerfile: "Dockerfile.custom"
build: "/path/to/build/context"
# Resource requests
resources:
requests:
cpu: 100 # CPU request in millicores
memory: 128 # Memory request in MB

Multiple containers can be defined within a single runnable, creating a pod-like deployment.

Services

Services define the endpoints that a runnable exposes (only valid in runnable entities):

services:
http:
container: web # Reference to the container name
port: 80 # Port the application listens on inside the container
protocol: tcp # tcp, udp, TCP, UDP, custom, CUSTOM
publish: true # Expose to external network
host-port: 8080 # Port exposed on the host machine (what clients connect to)
description: "HTTP endpoint"
custom-service:
container: app
port: 9000
protocol: custom # For custom protocols
description: "Custom protocol service"

Traffic sent to host-port on the host machine is forwarded to port inside the container.

custom protocol is used by custom entities to share values with other entities along the connection relationship. These connections are not happening on the network and thus do not require ports to be configured.

Connections

Connections define how a runnable connects to services provided by other entities (only valid in runnable entities):

connections:
db:
target: my-namespace/database # Path to the target entity
service: postgres # Service name to connect to
description: "Main database connection"
cache:
target: my-namespace/redis
service: redis
optional: true # Entity will start even if this connection fails
description: "Optional Redis cache"

Connections can be marked as optional - the entity will start even if optional connections are not available.

Files

Files define configuration files to be created in containers (only valid in runnable entities):

files:
nginx-conf:
container: web
path: /etc/nginx/nginx.conf
mode: 0644 # File permissions (octal)
contents: |
server {
listen 80;
# ...
}
raw: false # Whether to treat contents as raw data (default: false)
binary-file:
container: app
path: /app/config.bin
contents: "binary data here"
raw: true # Treat as raw data, do not interpolate variables

File names must only contain alphanumeric characters, dots, underscores, and hyphens.

Checks

Checks allow you to define health and readiness checks for your runnables (only valid in runnable entities):

checks:
readiness:
code: 'http-get("http://localhost:8080/health") nil? not'
period: 10 # Check interval in seconds
initialDelay: 5 # Wait before first check
attempts: 5 # Number of attempts before failing

The code field contains ArrowScript that should return a boolean value. The script is executed in the context of the runnable it is defined in.

Note: readyness is deprecated - use readiness instead.

Dependencies

Dependencies define which entities should become ready before the runnable can start.

depends:
wait-for:
runnables:
- my-namespace/database
- my-namespace/redis
timeout: 300 # Seconds

Scaling

Configure horizontal scaling for your runnables (only valid in runnable entities):

scale:
value: 3 # Static number of instances
# Or dynamic scaling
value: "cpu() 0.8 gt? then(5) else(3)" # ArrowScript without <- prefix
min: 1 # Minimum instances
max: 10 # Maximum instances
interval: 30 # Check interval in seconds
throttle-up: 120 # Seconds between scale ups
throttle-down: 300 # Seconds between scale downs
# Metric-based scaling
metrics:
cpu: 0.7 # Target CPU utilization (70%)
memory: 0.8 # Target memory utilization (80%)

Volumes

Define persistent storage volumes (only valid in runnable entities):

volumes:
data:
kind: ssd # Volume type: ssd, hdd, local, SSD, HDD, LOCAL
size: 10 # Size in GB
path: /data # Mount path
name: "my-volume" # Cloud volume name
# Backup configuration
backup:
every: 1
kind: day # hour, day, week
rotation-days: 7
start-time: "02:00" # 24-hour format
start-day: "MONDAY" # Day of week for weekly backups

Volume configuration varies by provider.

Affinity

Control where your runnable gets scheduled (only valid in runnable entities):

affinity:
tag: "production" # Run on nodes with this tag
# OR
name: "worker-1" # Run on a specific node
ignore-pressure: false # Consider node resource pressure
resident: true # Reserve the node exclusively

Affinity requires either tag or name to be specified.

Recovery

Configure how your runnable recovers from failures (only valid in runnable entities):

recovery:
after: 10 # Seconds before recovery (can also be string like "10s")
mode: default # Recovery mode: node, cluster, default
when: container-failure # Recovery trigger condition
kill-on-recovery: parent # What to kill during recovery: parent, child

Recovery trigger conditions include:

  • always
  • node-failure
  • container-failure
  • none
  • pressure
  • memory-pressure
  • cpu-pressure
  • pid-pressure
  • disk-pressure

Actions

Define custom actions that can be executed on entities (only valid in runnable entities):

actions:
backup:
description: "Create a backup of the application"
code: |
exec("app", "/bin/bash", "-c", "pg_dump mydb > /backup/dump.sql")
arguments:
target:
type: string
description: "Backup target location"
default: "/backup"
migrate:
description: "Run database migrations"
code: |
exec("app", "/bin/bash", "-c", `migrate --database ${args["db-url"]} up`)
arguments:
db-url:
type: string
description: "Database URL for migrations"

Actions can be executed using monk do <entity-path>/<action-name> [arg=value]*.

Requirements

Specify resource requirements for entities (only valid in runnable entities):

requirements:
CPU: "2.0" # Number of CPU cores required
RAM: 4096 # RAM in MB
GPU: true # Whether GPU is required
disk: 100 # Disk space in GB

Permitted Secrets

Define which secrets an entity is allowed to access:

permitted-secrets:
api-key: true
database-password: true
optional-secret: false

Metadata

Add descriptive information to your entities (valid in all entity types):

metadata:
name: "My Web Application"
description: "A simple web application with database"
website: "https://example.com"
source: "https://github.com/example/webapp"
publisher: "Example Corp"
icon: "https://example.com/icon.png"
tags: "web, database, production"
private: false # Whether to hide from catalog

Note: The name field is not required in metadata, though it’s recommended for better documentation.

ArrowScript

ArrowScript is an embedded scripting language that allows dynamic calculations and transformations within MonkScript. ArrowScript expressions begin with <- and can be used in most string or numeric fields within Monk YAML files. ArrowScripts are executed on Monk’s control plane at runtime instead of being simple macros.

ArrowScript has a concise concatenative syntax ideal for writing one-liners. You can think of it as Monk’s shell scripting language.

variables:
greeting: <- `Hello ${name}!` # Template strings
random-port: <- random(3000, 5000) # Operator calls
db-ready: <- $db "ready" eq # Value comparison

ArrowScript can be used for:

  • String interpolation a’la JavaScript template literals (e.g. <- \Hello ${name}!“)
  • Mathematical calculations (e.g. <- 5 10 add)
  • Conditional logic (e.g. <- $db "ready" eq then("ok") else("not ok"))
  • Accessing other variables with $variable-name
  • Built-in operators and functions like random(), http-get(), etc.
  • Accessing entity state with connection-target("connection-name") entity-state get-member("port")

In some contexts like scale.value and check.readiness.code, ArrowScript is written without the <- prefix. If the arrow was used, Monk would first compute the value of the expression and then pass it as the script to be executed.

For a complete reference of ArrowScript syntax, operators, and advanced features, see the ArrowScript Language Reference.

Entity Type Definitions and Instances

Entity type definitions allow you to create custom resources that extend Monk with your own data structures and logic. Entities can store structured data and execute custom JavaScript code during their lifecycle.

Entity Type Definition

An entity type is defined with defines: entity and can include a schema to validate its structure:

namespace: example
database-type:
defines: entity
schema:
name:
type: string
version:
type: string
port:
type: integer
variables:
default-port: 5432

The schema property describes the entity structure and follows JSON Schema specification, similar to OpenAPI.

Entity Instances

Once an entity type is defined, you can create instances of it:

postgres-db:
defines: example/database-type
name: "main-database"
version: "14"
port: 5432

When you create an instance:

  1. The instance inherits all properties from the entity type definition
  2. The instance must provide values that match the schema
  3. You can override variables and metadata in the instance

Lifecycle Scripts

Entity types can include JavaScript (ES6) lifecycle scripts that execute during different phases:

database-type:
defines: entity
lifecycle:
create: |
function main(definition, state, context) {
console.log("Creating database:", definition.name);
// Create database logic here
return { "created": Date.now() };
}
start: |
function main(definition, state, context) {
console.log("Starting database:", definition.name);
// Start database logic here
return { ...state, "started": Date.now() };
}
stop: |
function main(definition, state, context) {
console.log("Stopping database:", definition.name);
// Stop database logic here
}
purge: |
function main(definition, state, context) {
console.log("Purging database:", definition.name);
// Delete database logic here
}

The main function receives up to 3 arguments:

  • definition - Entity data as defined in template
  • state - Saved data from previous executions (can be empty)
  • context - Additional data with properties like action, status, path

The returned object is saved as state and passed to subsequent operations.

Available Lifecycle Events

  • create - Triggered with first monk run
  • start - Triggered with every monk run or monk update
  • update - Triggered with monk update
  • stop - Triggered with monk stop
  • purge - Triggered with monk purge
  • sync - Triggered for any command that has no explicit script

Custom Actions

You can define custom actions that can be called with monk do:

database-type:
defines: entity
lifecycle:
backup: |
function main(definition, state, context) {
console.log("Backing up database:", definition.name);
// Backup logic here
return { ...state, "lastBackup": Date.now() };
}

To use a custom action:

monk do example/postgres-db/backup

Readiness Checks

Entity types can include readiness checks to determine when they’re ready:

database-type:
defines: entity
checks:
readiness:
code: |
function main(definition, state, context) {
// Test if database is ready
if (!isReady()) {
throw new Error("Database not ready yet");
}
return state;
}
period: 10 # Check interval in seconds
initialDelay: 5 # Wait before first check
attempts: 12 # Max attempts (fails after this)

Requiring Modules

Entity scripts can use built-in modules for additional functionality:

database-type:
defines: entity
requires:
- http
- secret
- fs
lifecycle:
create: |
const http = require("http");
const secret = require("secret");
const fs = require("fs");
const helpers = require("helpers");
function main(def, state, ctx) {
// Use Secret module
let password = secret.get(def.secret);
// Use HTTP module
let response = http.get(`https://api.example.com/buckets/${def.name}`, {
headers: {
"Authorization": `Bearer ${password}`
}
});
// Use file system operations
let configFiles = fs.ls("/app/config");
// Use helpers for encoding
let encoded = helpers.btoa("sensitive-data");
return {
bucketName: response.body,
configCount: configFiles.length,
encodedData: encoded
};
}

Available modules include:

  • cli - CLI operations (output to command line)
  • secret - Secret management (get, set, remove secrets, generate random strings)
  • fs - File system operations (ls, readFile, zip, tar)
  • parser - Parsing utilities (xmlQuery, jsonQuery, htmlQuery)
  • http - HTTP client (get, post, put, delete, do methods)
  • vars - Variable management (get, set, has runtime variables)
  • helpers - Utility functions (error creation, base64 encoding/decoding)

Note: Cloud provider modules (aws, gcp, azure, digitalocean) are planned but not yet available.

Entity References

Entities can reference other entities using ArrowScript:

my-app:
defines: runnable
variables:
db-host: <- entity-state("example/postgres-db") get-member("address")
db-port: <- entity("example/postgres-db") get-member("port")
containers:
app:
environment:
- <- `DB_HOST=${db-host}`
- <- `DB_PORT=${db-port}`

This allows components to properly reference and use resources managed by custom entities.

Complete Entity Type Example

namespace: example
api-service:
defines: entity
schema:
required: ["name", "endpoint"]
name:
type: string
endpoint:
type: string
auth-token:
type: string
default: ""
requires:
- http
- secret
- helpers
lifecycle:
create: |
function main(def, state, ctx) {
const http = require("http");
const secret = require("secret");
const helpers = require("helpers");
console.log("Creating API service:", def.name);
// Store auth token as secret
if (def["auth-token"]) {
secret.set(`${def.name}-token`, def["auth-token"]);
}
// Test API endpoint
let response = http.get(def.endpoint + "/health");
if (response.statusCode !== 200) {
throw helpers.error("API endpoint not responding: " + response.status);
}
return {
"endpoint": def.endpoint,
"created": Date.now(),
"status": "active"
};
}
start: |
function main(def, state, ctx) {
const http = require("http");
const secret = require("secret");
console.log("Starting API service:", def.name);
// Verify service is accessible
let token = secret.get(`${def.name}-token`);
let response = http.get(def.endpoint + "/status", {
headers: token ? { "Authorization": `Bearer ${token}` } : {}
});
return {
...state,
"lastStarted": Date.now(),
"healthy": response.statusCode === 200
};
}
purge: |
function main(def, state, ctx) {
const secret = require("secret");
console.log("Purging API service:", def.name);
// Clean up stored secrets
secret.remove(`${def.name}-token`);
return {};
}
check-readiness: |
function main(def, state, ctx) {
const http = require("http");
const secret = require("secret");
let token = secret.get(`${def.name}-token`);
let response = http.get(def.endpoint + "/health", {
headers: token ? { "Authorization": `Bearer ${token}` } : {},
timeout: 5000
});
return {
...state,
"lastCheck": Date.now(),
"healthy": response.statusCode === 200,
"responseTime": response.contentLength
};
}

MANIFEST Files

MANIFEST files provide a way to organize and manage multiple MonkScript YAML files as a cohesive unit. They allow you to define the structure and loading order of your templates, making it easier to manage complex projects with multiple components.

Purpose and Benefits

MANIFEST files solve several organizational challenges:

  • Flexible Organization: Organize your YAML files in any directory structure you prefer
  • Load Order Control: Define the exact order in which templates are loaded
  • Metadata Management: Include project-level metadata
  • Resource Listing: Catalog all resources that belong to a project
  • Hierarchical Structure: Reference other MANIFESTs to create nested project structures

Complete Example

Here’s a complete example that demonstrates many MonkScript features:

namespace: example
# A web application
web-app:
defines: runnable
metadata:
name: "Example Web App"
description: "A web application with database"
tags: "web, example"
variables:
http-port: 8080
version: "1.0.0"
debug: true
log-level: <- $debug ? "debug" : "info"
containers:
web:
image: nginx
image-tag: <- $version
arch: amd64
ports:
- "80:80"
environment:
- <- `LOG_LEVEL=${log-level}`
user: 1000
restart: "unless-stopped"
privileged: false
cap-drop:
- "ALL"
memory: 536870912
logs:
quota: "100M"
count: 5
services:
http:
container: web
port: 80
protocol: tcp
publish: true
host-port: <- $http-port
files:
nginx-conf:
container: web
path: /etc/nginx/conf.d/default.conf
raw: false
contents: |
server {
listen 80;
location / {
root /usr/share/nginx/html;
}
}
checks:
readiness:
code: "http-get(\"http://localhost:80\") nil? not"
period: 5
attempts: 3
scale:
value: 2
min: 1
max: 5
actions:
reload:
description: "Reload nginx configuration"
code: |
exec("web", "/bin/bash", "-c", "nginx -s reload")
requirements:
CPU: "0.5"
RAM: 512
permitted-secrets:
tls-cert: true
api-key: false

This example defines a web application that uses variables and ArrowScript, and defines containers, services, files, checks, scaling options, actions, requirements, and permitted secrets.