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 followentity-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
# IncorrectMyEntity: # Don't use PascalCasemy_entity: # Don't use snake_casemyEntity: # 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 runnabledefines: group
- Creates a group (previouslyprocess-group
)defines: entity
- Creates an entity type definitiondefines: 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:
- Simple values in the current entity override values from the inherited entity
- Lists are combined (with duplicates removed)
- Maps/objects are deeply merged, with current values taking precedence for duplicate keys
- 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 definitionsvariables
- Entity variablesfiles
- File definitionsconnections
- Connections to other entitiesservices
- Service definitionschecks
- Health/readiness checksdepends
- Dependenciesrecovery
- Recovery configurationaffinity
- Scheduling configurationvolumes
- Persistent volume configurationscale
- Scaling configurationmetadata
- Entity metadatarequirements
- Resource requirementsactions
- Custom actionspermitted-secrets
- Secret access permissionsmonk-ui
- UI configurationversion
- Entity version
Group Sections
Groups support:
members
orrunnable-list
- List of entities to includevariables
- Entity variablesmetadata
- Entity metadataaffinity
- Scheduling configurationpermitted-secrets
- Secret access permissionsmonk-ui
- UI configuration
Entity Type Definition Sections
Entity type definitions support:
schema
- JSON Schema for validationvariables
- Entity variablesservices
- Service definitionsconnections
- Connection definitionsmetadata
- Entity metadatarequires
- Required JS moduleslifecycle
- Lifecycle hooks
Entity Instance Sections
Entity instances support:
variables
- Entity variablesmetadata
- Entity metadatamonk-ui
- UI configurationservices
- Service definitionsconnections
- Connection definitionschecks
- Health/readiness checksdepends
- 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 valuesint
- Integer numbersfloat
- Floating-point numbersbigint
- Large integer valuesbool
- 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 - usereadiness
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:
- The instance inherits all properties from the entity type definition
- The instance must provide values that match the schema
- 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 templatestate
- 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 firstmonk run
start
- Triggered with everymonk run
ormonk update
update
- Triggered withmonk update
stop
- Triggered withmonk stop
purge
- Triggered withmonk 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 applicationweb-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.