Advanced Runtime Examples
GraphQL API Chaining
The api engine supports declarative GraphQL queries alongside REST endpoints, enabling seamless integration with GraphQL servers.
# actions/query_infrastructure.toml
name = "query_infrastructure"
description = "Query infrastructure state via GraphQL"
[[input]]
name = "environment"
description = "Target environment"
type = "string"
required = true
[vault_secrets.metrics_token]
collection = "observability"
secret = "metrics_api_token"
[runtime]
engine = "http"
# Native GraphQL support
[[runtime.steps]]
method = "POST"
url = "https://api.internal/graphql"
graphql_query = """
query GetDeployments($env: String!) {
deployments(environment: $env) {
id
name
status
lastDeployed
}
}
"""
[runtime.steps.graphql_variables]
env = "{{ input.environment }}"
[[runtime.steps]]
method = "POST"
url = "https://metrics.internal/graphql"
headers = { "Authorization" = "Bearer ${secrets.metrics_token}" }
graphql_query = """
query GetMetrics($env: String!) {
metrics(environment: $env) {
cpu
memory
requests
}
}
"""
[runtime.steps.graphql_variables]
env = "{{ input.environment }}"
REST API Chaining with Templates
Chain multiple REST API calls with input and secret templating for orchestrated workflows.
# actions/provision_database.toml
name = "provision_database"
description = "Provision a database and configure backups via API chaining"
[[input]]
name = "db_name"
description = "Database name"
type = "string"
required = true
[[input]]
name = "instance_type"
description = "Instance type (small, medium, large)"
type = "string"
default = "medium"
[[mount]]
resource_type = "payload"
filename = "create_db_payload.json"
mount_path = "create_db_payload.json"
[vault_secrets.cloud_api_key]
collection = "cloud_credentials"
secret = "api_key"
[vault_secrets.provisioning_token]
collection = "internal_services"
secret = "provisioning_webhook_token"
[runtime]
engine = "http"
# Step 1: Create database instance
[[runtime.steps]]
method = "POST"
url = "https://api.cloud.internal/v1/databases"
headers = { "Authorization" = "Bearer ${secrets.cloud_api_key}", "Content-Type" = "application/json" }
body = "@create_db_payload.json"
# Step 2: Enable automated backups
[[runtime.steps]]
method = "POST"
url = "https://api.cloud.internal/v1/databases/{{ input.db_name }}/backups"
headers = { "Authorization" = "Bearer ${secrets.cloud_api_key}" }
body = """
{
"schedule": "0 2 * * *",
"retentionDays": 30,
"encrypted": true,
"storageClass": "standard"
}
"""
# Step 3: Notify provisioning system
[[runtime.steps]]
method = "POST"
url = "https://provisioning.internal/webhooks/db-created"
headers = { "X-Webhook-Secret" = "${secrets.provisioning_token}" }
body = """
{
"databaseName": "{{ input.db_name }}",
"timestamp": "{{ .now }}"
}
"""
Secure Signed Webhook Headers
The http engine supports HMAC-SHA256 signed payloads for securely triggering external CI/CD pipelines.
# actions/trigger_deployment.toml
name = "trigger_deployment"
description = "Securely trigger a deployment via signed webhook"
[[input]]
name = "version"
description = "Version to deploy"
type = "string"
required = true
[[input]]
name = "environment"
description = "Target environment"
type = "string"
required = true
[vault_secrets.github_webhook_secret]
collection = "github_credentials"
secret = "webhook_secret"
[vault_secrets.github_token]
collection = "github_credentials"
secret = "api_token"
[runtime]
engine = "http"
[[runtime.steps]]
method = "POST"
url = "https://github.com/repos/org/service/dispatches"
signature_secret = "${secrets.github_webhook_secret}"
signature_header = "X-Hub-Signature-256"
headers = { "Authorization" = "Bearer ${secrets.github_token}" }
When executing via webhook (fetch mode) directly from the Controller:
rescile-ce runner \
--inputs version=1.2.3,environment=production \
"http://127.0.0.1:7600/api/actions/my_module/trigger_deployment"
The runner automatically:
- Serializes inputs into a JSON payload
- Computes HMAC-SHA256 signature using the secret
- Sends
POSTrequest with signature header:X-Hub-Signature-256: sha256=<hex_signature> - GitHub (or any compatible system) verifies the signature before processing
Tip: You can securely interact with the Controller from within your actions. When a runner is connected via an API token, the
${secrets.rescile_controller_url}and${secrets.rescile_api_token}values are automatically injected into the action’s environment, allowing native queries back to the graph.
Dynamic Service Tunneling & Proxy Routing
A long-running service action owns its HTTP listener. Use [runtime.expose] for a runner-launched service, with protocol = "socket" (Unix) or protocol = "http" (TCP). Set auto_execute = true when service should start without a manual trigger.
# actions/start_acme_gateway.toml
name = "start_acme_gateway"
description = "ACME gateway"
run_mode = "service"
auto_execute = true
exec = [["python3", "acme_gateway.py"]]
[runtime]
engine = "nix"
flake = "./flake.nix"
files = ["./acme_gateway.py"]
[runtime.expose]
protocol = "socket"
env_var = "UNIX_SOCKET" # Application binds this allocated socket path
# For TCP instead: protocol = "http", env_var = "PORT"
Service must bind allocated UNIX_SOCKET (or PORT for TCP). A fixed TCP port may be set with port = 8080 under [runtime.expose]. Action can select its own upstream backends. In app/proxy.toml, route browser requests to action:
[[route]]
path = "/acme/*"
target = "action://cert-manager/start_acme_gateway/acme"
Controller selects one running action and forwards to its listener over runner’s existing outbound connection. Request does not start action. Keep browser links relative or configure application’s base path for /apps/cert-manager/acme/. See Action-owned service routes for tunnel limits, cookie scoping, URL rewriting, and supported headers.
WebAssembly (WASM) Execution
Execute compiled WebAssembly modules with strict sandboxing and efficient resource isolation. WASM modules must export a _start function and are executed with wasmtime.
# actions/validate_config.toml
name = "validate_config"
description = "Validate configuration using a WASM module"
[[input]]
name = "config_path"
description = "Path to configuration file"
type = "string"
required = true
default = "config.json"
[runtime]
engine = "wasi"
wasm = "./validator.wasm"
files = ["./config.json", "./schema.json"]
[[mount]]
resource_type = "config"
filename = "config.json"
mount_path = "config.json"
[[mount]]
resource_type = "schema"
filename = "schema.json"
mount_path = "schema.json"
Building WASM modules for rescile:
# Rust example with wasm-pack
wasm-pack build --target wasi --release
# The _start function serves as the entry point
#[no_mangle]
pub extern "C" fn _start() {
// Read from /workspace/config.json
// Validate against /workspace/schema.json
// Exit with status 0 on success, 1 on failure
}
WASM execution provides:
- Zero external dependencies: No syscall overhead
- Strict I/O isolation: Only explicit mounts are accessible
- High performance: Near-native execution speed
- Deterministic behavior: Same code always produces same output
Combining Engines: Multi-Stage Pipelines
Complex workflows can chain multiple engines in sequence:
# actions/deploy_with_validation.toml
name = "deploy_with_validation"
description = "Validate config (WASM) → Query state (GraphQL) → Deploy (Nix)"
[[input]]
name = "version"
type = "string"
required = true
# Stage 1: Native WASM Engine
[[stages]]
name = "validate"
[stages.runtime]
engine = "wasi"
wasm = "./validator.wasm"
args = ["{{ input.version }}"]
# Stage 2: Native API Engine
[[stages]]
name = "query_state"
[stages.runtime]
engine = "http"
[[stages.runtime.steps]]
method = "POST"
url = "https://api.internal/graphql"
graphql_query = """
query GetState($version: String!) {
state(version: $version) {
status
}
}
"""
[stages.runtime.steps.graphql_variables]
version = "{{ input.version }}"
# Stage 3: Native Nix Engine
[[stages]]
name = "deploy"
[stages.runtime]
engine = "nix"
flake = "./flake.nix"
exec = [
["nix", "flake", "update"],
["nix", "develop", "--command", "deploy.sh", "{{ input.version }}"]
]
Scheduled Actions (Calendar Triggers)
A schedule makes the controller queue the action at every due instant. Combine it with
action expansion to derive per-resource calendars from graph data — each application
below carries its own backup_schedule property, so one action file produces two
independent schedule bindings.
# actions/backup.toml
name = "backup_{{ origin_resource.name }}"
description = "Nightly backup for {{ origin_resource.name }}"
origin_resource = "application"
# Cron comes from the application row; timezone from a module parameter.
schedule = { cron = "{{ origin_resource.backup_schedule }}", timezone = "{{ params.backup_timezone }}" }
exec = [["python3", "backup.py", "{{ origin_resource.name }}"]]
[runtime]
engine = "nix"
flake = "./flake.nix"
files = ["./backup.py"]
# assets/application.csv
name,backup_schedule
billing,30 2 * * *
catalog,0 4 * * SUN
The controller validates each rendered schedule after the graph build. A bad expression
(such as a missing backup_schedule property) disables only that binding and surfaces
a diagnostic on GET /api/schedules — the rest of the workspace keeps building and
serving.
Fixed schedules work without any graph data:
# actions/rotate_keys.toml
name = "rotate_keys"
description = "Rotate API keys on the last Friday of the month"
schedule = "0 3 * * 5#L" # last Friday, 03:30 UTC
# schedule = { cron = "0 3 * * 5#L", timezone = "Europe/Zurich" } — with explicit timezone
exec = [["python3", "rotate.py"]]
[runtime]
engine = "api"
[[runtime.steps]]
method = "POST"
url = "https://api.internal/v1/keys/rotate"
See Scheduled Actions for pattern syntax, missed-occurrence and overlap semantics, and observability.