1. Prerequisites
Docker runs your R code in an isolated container so it behaves the same on your laptop and on the Aire Labs platform. Install one of these:- OrbStack (recommended for macOS) — fast, lightweight, low battery usage.
- Docker Desktop for Mac — the official Docker application.
- Docker Desktop for Windows
- Docker Engine for Linux
You should see
Hello from Docker! in the output. If you get Cannot connect to the Docker daemon, open OrbStack or Docker Desktop and try again.2. Quick start
Clone the example and build the Docker image. This downloads R and installs thejsonlite package inside the container. It takes about 2 minutes the first time; subsequent builds are fast.
The last line of output should say something like
naming to docker.io/library/lcoe-r done.Apple Silicon (M1/M2/M3)If you see This is not a blocking error on all setups — Docker can sometimes emulate amd64 on arm64 — but pulling the native image avoids the warning and runs faster.
Base image was pulled with platform "linux/amd64", expected "linux/arm64", your local Docker cache has the wrong architecture for the base image. Pull the correct variant and rebuild:You should see:
OK — dataset=solar, year=2027, lcoe=43.39 USD/MWh3. How it works
When a model contains a container function block, the Aire Labs compute engine resolves the input values, writes them to a JSON file, runs your container, reads the output JSON, and writes the results back into the model. Thedocker run command you just ran simulates that same flow locally. See the Docker getting started guide for more on volumes (-v) and environment variables (-e).
The input file
Openfixtures/hook-input.json. This is the format the platform uses to pass values from your model into your R code:
fixtures/hook-input.json
name and one typed value field. Numbers are always strings ("0.08" not 0.08) to preserve decimal precision. The optional unit field carries the unit of measure.
Each parameter name corresponds to an input node in your Aire Labs model. When you wire a container function block into a model, you map model terms to these parameter names. The platform reads the current value of each mapped term and passes it to your code via this JSON file.
The output file
Your R code writes a JSON file with the same structure, using aresults array instead of parametersV1:
/tmp/airelabs/hook-output.json
Your code defines the output names. Each
name in the results array becomes an output on the container function block in your Aire Labs model. The platform reads this file after your container exits and writes each value back into the model, making them available to downstream formulas.Your container runs in a secure sandbox on the Aire Labs platform: read-only filesystem (except
/tmp), no network access, and configurable memory/CPU limits. The only way to communicate with the platform is through these JSON files.4. The R code
The example has three R files. Open each one as you read through this section.main.R — the entry point
Reads input parameters, loads cost assumptions from a bundled CSV, computes LCOE, and writes the results. The JSON plumbing is handled by helpers inR/airelabs.R:
main.R
main.R wraps this in a tryCatch so the container exits with code 1 on any error, which tells the platform the invocation failed.
R/model.R — the LCOE computation
Pure R. No knowledge of JSON, Docker, or the Aire Labs platform. You cansource() it in RStudio and call compute_lcoe() directly.
R/model.R
R/data_lookup.R — loading bundled data
Containers have no network access, so reference data must be built into the Docker image. Thedata/ directory contains CSV files with year-by-year cost assumptions for solar and wind. The load_cost_assumptions() function looks up a row by dataset name and year, and stops with a clear error if there’s no match.
R/airelabs.R — JSON helpers
Handles reading the input JSON and writing the output JSON. Copy it as-is into any container function project. The key functions:read_hook_input()— reads input JSON, returns a data framerequire_number(),require_string()— extract a parameter by name (stops if missing)number_result(),string_result(),error_result()— build result entrieswrite_hook_output()— writes results as JSON
5. Different inputs
Copy a different fixture into/tmp/airelabs/hook-input.json and re-run the same docker run command from the quick start.
Wind instead of solar
lcoe=44.81 USD/MWh for wind 2030.
Invalid discount rate (error result)
This fixture passes a negative discount rate. Instead of crashing, the container returns anerror result on the LCOE output — the platform marks that cell as an error while the other outputs still get values.
The output JSON will have
"lcoe": { "error": "INVALID_DISCOUNT_RATE" } — not a number.Unknown dataset (hard error)
This should fail with:
Error: unknown dataset 'geothermal' — available datasets: solar, wind. Unlike the invalid discount rate (which returned partial results), this is a hard error — exit code 1, no output file written.Run the tests
Both should end with
All model tests passed. or All integration tests passed.6. Writing your own function
Replace the example with your own computation:- Write your model in
R/model.R. Keep it pure R — no JSON, no Docker, no platform code. It should take arguments and return a list. Develop and test it in RStudio the way you normally would. - Edit
main.Rto read the parameters your model needs (usingrequire_number,require_string, etc.) and write the results it produces (usingnumber_result,string_result, etc.). - Create a fixture file in
fixtures/with the parameters your model expects. Use an existing fixture as a template. - If you need more R packages, add them to the
RUN Rscript -eline in the Dockerfile:
Dockerfile
- Rebuild and test:
R/airelabs.R helper module works for any container function — copy it as-is into new projects.
7. The Dockerfile
Here is what the Dockerfile does, line by line:Dockerfile
FROM rocker/r-base:latest— starts from the Rocker base image, which includes R and common system libraries.RUN Rscript -e "install.packages(...)"— installs R packages during the build, so they’re available at runtime without network access.COPY . .— copies your project (R files, CSV data, fixtures) into the image.CMD ["Rscript", "main.R"]— the default command when the container starts.
Choosing a base image
The Rocker project provides several image variants:| Image | What’s included | Use when |
|---|---|---|
rocker/r-base | Minimal R | Simple scripts with few packages |
rocker/r-ver:4.4.0 | Pinned R version | You need reproducible builds |
rocker/tidyverse | R + dplyr, ggplot2, tidyr, etc. | Using tidyverse packages |
Adding packages
To add R packages, extend theRUN Rscript -e line. Some packages (like httr or xml2) also need system C libraries — see the Rocker extending images guide. For reproducible builds, pin your R version with rocker/r-ver:4.4.0 instead of :latest.
8. Common questions
Can I use multiple R files? Yes. There is no limit to the number of R files in your container. The example uses three files (main.R, R/model.R, R/airelabs.R) and you can add as many as you need. Use source() in main.R to load them. Everything inside your project directory gets copied into the Docker image by COPY . . in the Dockerfile, so any file structure that works locally will work in the container.
How do output names work?
Output names are entirely up to you. The name field in each result entry (e.g. "lcoe") is defined by your R code and determines how the output appears in Aire Labs under the block that represents your container function. You do not need to pre-declare them anywhere — the platform reads the output JSON your container produces and creates the corresponding outputs on the block automatically.
Can inputs come from the Aire Labs model?
Yes — that is exactly how inputs work. When you configure a container function block in the model, you map model terms to input parameter names. The platform reads the current value of each mapped term and passes it to your code via the input JSON file. Inputs can be any term in the model: manual inputs, formula results, or outputs from other container functions.
9. What’s next
Once your container function works locally — it reads fixture files, produces the correct output, and passes your tests — the next step is publishing it to the Aire Labs platform so it can run inside your models.Coming soonA deployment guide covering how to connect your GitHub repository, configure the build pipeline, and wire your container function into an Aire Labs model is in progress. In the meantime, reach out to support@airelabs.com and we’ll help you get set up.
10. Reference
Supported value types
Both input parameters and output results support these types. Set exactly one value field per entry.| Field | JSON format | Example |
|---|---|---|
number | { value, unit? } | { "value": "12500000", "unit": "USD" } |
numberArray | { values, unit? } | { "values": ["100", "200"], "unit": "USD/MWh" } |
string | string | "Solar Farm Alpha" |
stringArray | { values } | { "values": ["wind", "solar"] } |
boolean | string | "true" |
booleanArray | { values } | { "values": ["true", "false"] } |
date | string (YYYY-MM-DD) | "2025-03-12" |
dateArray | { values } | { "values": ["2025-03-12", "2025-09-01"] } |
timestamp | string (ISO 8601) | "2025-03-12T14:30:00Z" |
timestampArray | { values } | { "values": ["2025-03-12T14:30:00Z"] } |
error | string | "INVALID_DISCOUNT_RATE" |
"true" or "false", not JSON booleans. The error type signals that a specific output could not be computed.
Environment variables
The platform sets these before your container starts:| Variable | Description |
|---|---|
AIRELABS_HOOK_INPUT_PATH | Path to the input JSON file |
AIRELABS_HOOK_OUTPUT_PATH | Where to write the output JSON file |
AIRELABS_ORGANIZATION_ID | Organization that owns the project |
AIRELABS_PROJECT_ID | Project containing the model |
AIRELABS_SCENARIO_ID | Scenario being computed |
AIRELABS_BLOCK_ID | Container function block node ID |
AIRELABS_INVOCATION_ID | Unique ID for this invocation |
R + JSON gotchas
The helpers inairelabs.R handle these, but if you’re writing your own JSON serialization:
- Always pass
auto_unbox = TRUEtowrite_json()— otherwise R wraps single values in arrays. See the jsonlite quickstart. - Build results with
list(), notdata.frame(). - Use
format(..., scientific = FALSE)for numbers — the platform rejects scientific notation. - Booleans are the strings
"true"/"false", not R’sTRUE/FALSE.
Reproducible R environments
For projects with many dependencies, use renv to lock package versions. See the renv Docker vignette for a complete example.JSON schemas
Machine-readable schemas for editor autocompletion and validation:- Input:
https://airelabs.studio/schemas/v1/airelabs.v1.HookInput.jsonschema.strict.bundle.json - Output:
https://airelabs.studio/schemas/v1/airelabs.v1.HookOutput.jsonschema.strict.bundle.json
Further reading
- Container Functions overview — the full protocol spec, Python implementation, and JSON Schema validation
- Docker getting started — official introduction to Docker concepts
- Rocker project — Docker images for R
- jsonlite on CRAN — R ↔ JSON conversion
