Building OCI-Compliant Container Images
You can build OCI-compliant container images natively on RWX.
Building images on RWX is substantially faster and more ergonomic than building images with docker build
due to techniques detailed in the original proposal.
However, note that building with docker build on RWX is also supported.
Simple Example
Here is a simple example that builds a container from a base of ubuntu:24.04.
It creates a script and then configures the container command to run the script.
base:
image: ubuntu:24.04
config: none
tasks:
- key: image
run: |
cat <<'EOF' > script.sh
#!/usr/bin/env bash
echo "hello from a container built on RWX"
EOF
chmod +x script.sh
echo "$PWD/script.sh" | tee $RWX_IMAGE/command
To use the container image, you'll want to push it to a container registry
by using the rwx cli.
You'll need the task ID from the image task and you'll need to specify the remote tag.
rwx push the-task-id --to the-remote-image-tag
For example, if the ID of the image task is bf3c1467e60eb5601ef2a43b840a0d96 and you want to push the image to 123456789012.dkr.ecr.us-east-2.amazonaws.com/your_repository:your_tag, you would run:
rwx push bf3c1467e60eb5601ef2a43b840a0d96 \
--to 123456789012.dkr.ecr.us-east-2.amazonaws.com/your_repository:your_tag
For more details see the reference docs on pushing.
Full Node.js Example on RWX
https://github.com/rwx-cloud/rwx-image-example
Here is a more fully featured example of building a container image to run a Node.js application, pushing it directly from an RWX run, and then testing it within an RWX run.
High Level Approach
We're going to define three run definitions using embedded runs:
- build the container
- push the container
- test the container
on:
github:
push:
init:
commit-sha: ${{ event.git.sha }}
tasks:
- key: build-image
call: ${{ run.dir }}/build-image.yml
init:
commit-sha: ${{ init.commit-sha }}
- key: push-image
call: ${{ run.dir }}/push-image.yml
init:
image-task-id: ${{ tasks.build-image.tasks.image.id }}
image-tag: ${{ init.commit-sha }}
- key: test-image
after: push-image
call: ${{ run.dir }}/test-image.yml
init:
image-tag: ${{ init.commit-sha }}
Building the Container
https://github.com/rwx-cloud/rwx-image-example/blob/main/.rwx/build-image.yml
This example is defined in .rwx/build-image.yml
We'll use a base image of node:24.11.0-trixie-slim
base:
image: node:24.11.0-trixie-slim
config: none
Next, we'll define a system task to install curl and jq.
Those utilities are needed by the git/clone package that we'll use later.
You can also use this task to install any other packages that you need for your image.
tasks:
- key: system
run: |
apt-get -y update
apt-get -y install curl jq
apt-get -y clean
We'll then use the git/clone package to clone the code.
- key: code
use: system
call: git/clone 1.8.0
with:
repository: https://github.com/rwx-cloud/rwx-image-example.git
ref: ${{ init.commit-sha }}
We'll define another task to install npm packages.
We'll use a filter so that this task will always be cached unless package.json or package-lock.json changes.
Note that this is different than a normal Dockerfile where you have to COPY the files into the image first.
On RWX, you can start with all of the files, and then filter individual commands.
For more on the differences, see the docs on building images.
- key: npm-install
use: code
run: npm ci --omit=dev
filter:
- package.json
- package-lock.json
Finally, we'll define a task for the final image configuration.
We'll set the $RWX_IMAGE/user to run the image as the node user instead of root.
We'll also configure the default image command as node server.js.
- key: image
use: npm-install
run: |
# drop root privileges and run as the node user
echo "node" | tee $RWX_IMAGE/user
echo "node server.js" | tee $RWX_IMAGE/command
env:
NODE_ENV: production
For more documentation, see the reference docs on building images.
Pushing the Image from RWX
https://github.com/rwx-cloud/rwx-image-example/blob/main/.rwx/push-image.yml
Let's also define a run in .rwx/push-image.yml to push the image.
We're defining this as a separate workflow since pushing an image requires authenticating into a registry and installing tools that we may not want to have included in our container image.
For this run, we'll use the recommended ubuntu:24.04 base configured with rwx/base 1.0.0
base:
image: ubuntu:24.04
config: rwx/base 1.0.0
In this example we're going to push to an AWS ECR registry, but you can push to any container registry that you want.
We'll need to install the AWS CLI and the RWX CLI.
tasks:
- key: aws-cli
call: aws/install-cli 1.0.7
- key: rwx-cli
call: rwx/install-cli 2.0.2
Next, we'll use the aws/assume-role package to facilitate assuming a role that we've configured with permissions to push into our registry.
- key: aws
use: aws-cli
call: aws/assume-role 2.0.5
with:
region: us-east-2
role-to-assume: arn:aws:iam::537248441529:role/ecr-demo
role-duration-seconds: 900
Finally, we'll define a task to push the image.
- key: push-image
use: [rwx-cli, aws]
run: |
aws ecr get-login-password --region us-east-2 \
| docker login --username AWS --password-stdin $REGISTRY_HOST
rwx push $IMAGE_TASK_ID --to $REGISTRY_HOST/demo:$REMOTE_TAG
env:
AWS_OIDC_TOKEN: ${{ vaults.default.oidc.ecr-demo }}
IMAGE_TASK_ID: ${{ init.image-task-id }}
REGISTRY_HOST: 537248441529.dkr.ecr.us-east-2.amazonaws.com
REMOTE_TAG: ${{ init.image-tag }}
Using the Image in RWX
https://github.com/rwx-cloud/rwx-image-example/blob/main/.rwx/test-image.yml
Here's an example of pulling the image and using it within a run.
We'll create this run definition as .rwx/test-image.yml and use the recommended base of ubuntu:24.04 configured with rwx/base 1.0.0
base:
image: ubuntu:24.04
config: rwx/base 1.0.0
To pull the image for this example, we'll need to configure AWS to pull from our ECR repository. You can use any container repository that you want though.
tasks:
- key: aws-cli
call: aws/install-cli 1.0.7
- key: aws
use: aws-cli
call: aws/assume-role 2.0.5
with:
region: us-east-2
role-to-assume: arn:aws:iam::537248441529:role/ecr-demo
role-duration-seconds: 900
Next, we'll define a task to pull the image.
We'll use the docker: preserve-true option (see docs) to store the docker data in the RWX cache.
- key: docker-images
use: aws
docker: preserve-data
run: |
aws ecr get-login-password --region us-east-2 \
| docker login --username AWS --password-stdin $REGISTRY_HOST
docker pull $REGISTRY_HOST/demo:$IMAGE_TAG
env:
AWS_OIDC_TOKEN: ${{ vaults.default.oidc.ecr-demo }}
IMAGE_TAG: ${{ init.image-tag }}
REGISTRY_HOST: 537248441529.dkr.ecr.us-east-2.amazonaws.com
Finally, we can use the image.
We'll run it as a background process.
Configuring a health check is recommended to make sure the background process is fully ready before the run command is executed.
- key: test-image
docker: true
use: docker-images
background-processes:
- key: app
run: |
docker run --init -p 3000:3000 \
--health-cmd="curl -f http://localhost:3000" \
--health-interval=1s \
--health-retries 10 \
$REGISTRY_HOST/demo:$IMAGE_TAG
ready-check: rwx-docker-ready-check
run: curl -fsS http://localhost:3000
env:
AWS_SKIP_AUTH: true
IMAGE_TAG: ${{ init.image-tag }}
REGISTRY_HOST: 537248441529.dkr.ecr.us-east-2.amazonaws.com
Bringing It All Together
https://github.com/rwx-cloud/rwx-image-example/blob/main/.rwx/push.yml
Here is a full run definition configured to run on a GitHub push trigger that builds the image, pushes it to a registry, and then uses it in another workflow.
on:
github:
push:
init:
commit-sha: ${{ event.git.sha }}
tasks:
- key: build-image
call: ${{ run.dir }}/build-image.yml
init:
commit-sha: ${{ init.commit-sha }}
- key: push-image
call: ${{ run.dir }}/push-image.yml
init:
image-task-id: ${{ tasks.build-image.tasks.image.id }}
image-tag: ${{ init.commit-sha }}
- key: test-image
after: push-image
call: ${{ run.dir }}/test-image.yml
init:
image-tag: ${{ init.commit-sha }}
More Documentation
See the references docs on building images and pushing images.