Documentation Index Fetch the complete documentation index at: https://docs.reasonos.com/llms.txt
Use this file to discover all available pages before exploring further.
Container Building (OCI SDK)
RBS includes a built-in OCI SDK for building container images as part of your build process. No Docker daemon or Dockerfile is required — images are assembled programmatically using RBS DSL primitives.
Why RBS for Containers?
Feature RBS OCI Docker Build Buildpacks Daemon Required No Yes Yes Reproducible Yes (hermetic) Partial Partial Layer Control Full Limited None Build Integration Native Separate tool Separate tool Base Image Pull Built-in Built-in Built-in
Quick Start
Using High-Level Rules
The simplest way to build container images:
load( "@rbs//java/rules.rbs" , "java_binary" )
load( "@rbs//oci/rules.rbs" , "oci_image" )
# Build your application
java_binary(
name = "server" ,
srcs = glob([ "src/**/*.java" ]),
main = "com.example.Server" ,
deps = [ "@external://spring_boot_web" ],
)
# Package it as a container image
oci_image(
name = "server_image" ,
binary = ":server" ,
base = "eclipse-temurin:17-jre" ,
tag = "my-app:latest" ,
ports = [ 8080 ],
env = { "SPRING_PROFILES_ACTIVE" : "production" },
)
# Build the image
rbs build :server_image
# The image tar is saved to .rbs/bin/{platform}/server_image/image.tar
# Load it into Docker:
docker load < .rbs/bin/darwin-arm64/server_image/image.tar
OCI SDK Primitives
For advanced use cases, the OCI SDK provides low-level primitives accessible via ctx.oci within custom rules.
ctx.oci.create_layer()
Creates a single OCI layer from files.
def _my_image_impl ( ctx ):
# Create a layer with application files
app_layer = ctx.oci.create_layer(
name = "app" ,
files = {
"/app/server.jar" : ctx.bin.output + "/server.jar" , # dest: source
"/app/config.yaml" : "config/production.yaml" ,
},
directory = "/app" , # Set working directory
entrypoint = [ "java" , "-jar" , "/app/server.jar" ],
)
# Create a layer with static assets
assets_layer = ctx.oci.create_layer(
name = "assets" ,
files = {
"/app/static/" : "frontend/dist/" , # Copy entire directory
},
)
Layer Parameters
Parameter Type Description namestring Unique name for the layer. filesdict Mapping of destination_path: source_path. tarstring Path to an existing tar file to use as the layer. directorystring Sets the working directory (WORKDIR). entrypointlist Sets the container entrypoint. cmdlist Sets the container CMD (default arguments). envdict Environment variables to set. portslist Ports to expose. userstring User/group to run as (e.g., "1000:1000"). labelsdict OCI image labels.
ctx.oci.pull()
Pulls a base image from a registry.
def _my_image_impl ( ctx ):
# Pull a base image
base = ctx.oci.pull(
image = "ubuntu:22.04" ,
platform = "linux/amd64" , # Optional: specify platform
)
# Pull from a private registry
private_base = ctx.oci.pull(
image = "registry.example.com/base:latest" ,
credentials = {
"username" : "env:REGISTRY_USER" ,
"password" : "env:REGISTRY_PASS" ,
},
)
ctx.oci.write_docker_tar()
Assembles layers into a Docker-compatible image tarball.
def _my_image_impl ( ctx ):
base = ctx.oci.pull( image = "ubuntu:22.04" )
app_layer = ctx.oci.create_layer(
name = "app" ,
files = { "/app/main" : ctx.bin.executable},
entrypoint = [ "/app/main" ],
)
# Write the final image
ctx.oci.write_docker_tar(
output = ctx.bin.output + "/image.tar" ,
base = base,
layers = [app_layer],
tag = "my-app:latest" ,
config = {
"Env" : [ "APP_ENV=production" ],
"ExposedPorts" : { "8080/tcp" : {}},
"WorkingDir" : "/app" ,
},
)
ctx.oci.image_builder()
A fluent builder API for creating images step by step.
def _my_image_impl ( ctx ):
builder = ctx.oci.image_builder( base = "node:20-slim" )
# Add layers fluently
builder.add_layer(
name = "deps" ,
files = { "/app/node_modules/" : ctx.bin.run_files + "/node_modules/" },
)
builder.add_layer(
name = "app" ,
files = { "/app/" : ctx.bin.run_files + "/src/" },
directory = "/app" ,
entrypoint = [ "node" , "index.js" ],
)
builder.set_env({ "NODE_ENV" : "production" })
builder.expose_ports([ 3000 ])
builder.set_user( "1000:1000" )
# Build and write
builder.write(
output = ctx.bin.output + "/image.tar" ,
tag = "my-node-app:latest" ,
)
Complete Examples
Java Spring Boot Container
load( "@rbs//java/rules.rbs" , "java_binary" )
load( "@rbs//oci/rules.rbs" , "oci_image" )
java_binary(
name = "api_server" ,
srcs = glob([ "src/**/*.java" ]),
main = "com.example.api.Application" ,
deps = [
"@external://spring_boot_web" ,
"@external://spring_boot_actuator" ,
],
javaopts = [ "-Xmx512m" ],
)
oci_image(
name = "api_image" ,
binary = ":api_server" ,
base = "eclipse-temurin:17-jre-alpine" ,
tag = "api-server:latest" ,
ports = [ 8080 , 8081 ],
env = {
"SPRING_PROFILES_ACTIVE" : "production" ,
"JAVA_OPTS" : "-Xmx512m -XX:+UseG1GC" ,
},
labels = {
"org.opencontainers.image.title" : "API Server" ,
"org.opencontainers.image.version" : "1.0.0" ,
},
)
Node.js Express Container
load( "@rbs//nodejs/rules.rbs" , "nodejs_binary" )
load( "@rbs//oci/rules.rbs" , "oci_image" )
nodejs_binary(
name = "web_app" ,
srcs = glob([ "src/**/*.js" ]),
main = "src/index.js" ,
deps = [
"@external://express_repo" ,
"@external://cors_repo" ,
],
)
oci_image(
name = "web_image" ,
binary = ":web_app" ,
base = "node:20-slim" ,
tag = "web-app:latest" ,
ports = [ 3000 ],
env = { "NODE_ENV" : "production" },
)
Python Django Container
load( "@rbs//python/rules.rbs" , "py_binary" )
load( "@rbs//oci/rules.rbs" , "oci_image" )
py_binary(
name = "django_app" ,
srcs = glob([ "**/*.py" ]),
main = "manage.py" ,
deps = [
"@external://django_repo" ,
"@external://gunicorn_repo" ,
],
args = [ "runserver" , "0.0.0.0:8000" ],
)
oci_image(
name = "django_image" ,
binary = ":django_app" ,
base = "python:3.11-slim" ,
tag = "django-app:latest" ,
ports = [ 8000 ],
env = {
"DJANGO_SETTINGS_MODULE" : "myapp.settings.production" ,
"PYTHONDONTWRITEBYTECODE" : "1" ,
},
)
Multi-Stage Build (Custom Rule)
For maximum control over the image layout:
load( "@native//:defs" , "native" )
def _optimized_image_impl ( ctx ):
dirs = ctx.bin.create_dirs()
# Pull base image
base = ctx.oci.pull( image = ctx.attr.base)
# Layer 1: Runtime dependencies (rarely changes — cached efficiently)
deps_layer = ctx.oci.create_layer(
name = "dependencies" ,
files = { "/app/deps/" : dirs.run_files + "/deps/" },
)
# Layer 2: Application code (changes frequently)
app_layer = ctx.oci.create_layer(
name = "application" ,
files = { "/app/" : dirs.run_files + "/src/" },
directory = "/app" ,
entrypoint = ctx.attr.entrypoint,
env = ctx.attr.env,
ports = ctx.attr.ports,
user = "1000:1000" ,
)
# Write the image
ctx.oci.write_docker_tar(
output = dirs.output + "/image.tar" ,
base = base,
layers = [deps_layer, app_layer],
tag = ctx.attr.tag,
)
native.define_rule(
name = "optimized_image" ,
kind = "container" ,
implementation = _optimized_image_impl,
attrs = {
"binary" : attr.label( is_dep = True , mandatory = True ),
"base" : attr.string( default = "ubuntu:22.04" ),
"tag" : attr.string( mandatory = True ),
"entrypoint" : attr.string_list( default = []),
"env" : attr.string_dict( default = {}),
"ports" : attr.list( default = []),
},
)
Image Optimization Tips
Place rarely-changing content (OS packages, runtime dependencies) in early layers and frequently-changing content (application code) in later layers. This maximizes layer cache reuse.
Use slim or distroless base images to reduce image size and attack surface:
eclipse-temurin:17-jre-alpine instead of eclipse-temurin:17-jdk
node:20-slim instead of node:20
python:3.11-slim instead of python:3.11
Always run containers as a non-root user: app_layer = ctx.oci.create_layer(
name = "app" ,
files = { ... },
user = "1000:1000" ,
)
Output
Built images are saved as Docker-compatible tarballs:
.rbs/bin/{platform}/{package}/{target_name}/
└── image.tar # Docker-compatible tar file
Loading into Docker
docker load < .rbs/bin/darwin-arm64/server_image/image.tar
docker run -p 8080:8080 my-app:latest
Pushing to a Registry
docker load < .rbs/bin/darwin-arm64/server_image/image.tar
docker tag my-app:latest registry.example.com/my-app:latest
docker push registry.example.com/my-app:latest