CLI Quick Start (Docker)#

Image Composer Tool (ICT) builds custom Linux OS images, such as bootable RAW disk images and ISO installers, from YAML templates. This guide builds an OS image from a template you write, with the image-composer-tool command-line tool running inside a Docker container, and puts the result in a directory on your host. The host needs only Docker and a checkout of this repository: the ICT container image carries the tool and every program a build runs, such as debootstrap, mkfs, and grub.

Warning: the build container is privileged, and therefore root-equivalent on the host. Building an OS image creates loop devices, mounts filesystems, and chroots into them, so the container runs with --privileged and the host’s /dev mounted. Build only templates, and files they reference, that you trust.

How It Works#

scripts/build-in-container.sh runs on your host. It starts the container with the mounts below and runs image-composer-tool build inside it. When the build succeeds, it copies the OS image and its software bill of materials (SBOM) into your output directory, owned by you rather than by root.

On the host

Inside the container

Purpose

The template’s directory, or --context DIR

/input (read-only)

The template and the files it references

Your output directory, created if missing

/output, or --mount-point DIR

Receives the OS image

Named volume ict-cache, or --cache-volume NAME

/opt/ict/cache

Downloaded packages, reused by later builds

Named volume ict-workspace, or --workspace-volume NAME

/opt/ict/workspace

The build’s work tree, reused by later builds

Prerequisites#

  • A Linux x86_64 host with Git and Docker Engine. Your user must be allowed to run docker, which means being in the docker group; with sudo instead, the copied files belong to root.

  • At least 15 GB of free disk space. The container image and Docker’s build cache take about 5 GB, and a minimal RAW image needs about 5 GB more while it builds. Larger images, such as ISO installers, need more.

  • Network access to the package repositories your image downloads from. Behind a proxy, read Behind a Proxy first.

1. Build the ICT Container Image#

git clone https://github.com/open-edge-platform/image-composer-tool.git
cd image-composer-tool
docker build -f build/ict/Dockerfile --target ict -t ict .

The first build takes several minutes; later builds reuse Docker’s cache. Run it again after git pull so the image picks up the new commits.

2. Write a Template#

A template is a YAML file that describes the OS image: the distribution, the disk layout, the packages, the users, and so on. Only image and target are required. ICT takes everything else from the defaults for the target, here config/osv/ubuntu/ubuntu24/imageconfigs/defaultconfigs/default-raw-x86_64.yml, so this template builds a minimal Ubuntu 24.04 RAW disk image:

mkdir -p ~/my-image
cat > ~/my-image/my-image.yml <<'EOF'
image:
  name: my-image       # output files are named <name>-<version>.<format>
  version: "24.04"
target:
  os: ubuntu
  dist: ubuntu24
  arch: x86_64
  imageType: raw
EOF

To change a default, add its field to the template. The template reference describes every field, and the templates in image-templates/ are complete examples. To check the template before you build it:

docker run --rm -v ~/my-image:/input:ro ict image-composer-tool validate /input/my-image.yml

Every run of the container first prints PREFLIGHT OK: ..., its check that every program ICT may run is present.

3. Build the OS Image#

scripts/build-in-container.sh ~/my-image/my-image.yml ./out

The build log streams to your terminal; a minimal image takes several minutes the first time and less afterwards. When the build succeeds, the script copies its outputs into ./out:

out/
└── Default_Raw/                                     # systemConfig.name, from the defaults
    ├── my-image-24.04.raw.gz                        # the disk image, gzip-compressed
    └── spdx_manifest_deb_my-image_<timestamp>.json  # the SBOM

The disk.artifacts list, which this template takes from the defaults, decides which formats you get. ISO templates also produce a second directory, holding the installer’s initial RAM disk. Paths in the build log are inside the container, where ./out is mounted at /output.

Arguments after -- go to image-composer-tool build; run docker run --rm ict image-composer-tool build --help to list them. A file path among them is read inside the container, so give it as /input/<path relative to the context directory>:

scripts/build-in-container.sh ~/my-image/my-image.yml ./out -- --hostname edge-01 --log-level debug

Choose What Is Mounted#

Pass --mount-point to mount your output directory somewhere other than /output inside the container:

scripts/build-in-container.sh --mount-point /artifacts ~/my-image/my-image.yml ./out

The files still land in ./out on the host; only the path inside the container, which the build log shows, changes. The mount point must be an absolute path other than /, and must not be or sit under /input, /dev, or /opt/ict, where it would hide files the build needs.

Relative paths in a template resolve from its directory, as they do outside a container, but the build sees only the mounted directory. If your template references a file outside its own directory, for example an additional file at ../files/motd, mount a directory that contains everything with --context:

scripts/build-in-container.sh --context ~/templates ~/templates/edge/my-image.yml ./out

Some templates in image-templates/ reference files elsewhere in the checkout, such as under config/; build those with --context . from the checkout’s root.

Behind a Proxy#

Export your proxy settings in your shell before the first command, git clone:

export http_proxy=http://proxy.example.com:911
export https_proxy=http://proxy.example.com:912
export no_proxy=localhost,127.0.0.1

Then run the commands in this guide that reach the network as in the right column. Only docker build changes: it gives the settings to the image build only as build arguments, and a bare --build-arg NAME passes your shell’s value. Git reads the exported settings itself, and the script passes them into the container, so those commands stay the same. The other commands in this guide, such as validate, do not use the network.

Without a proxy Behind a proxy
git clone \
  https://github.com/open-edge-platform/image-composer-tool.git
git clone \
  https://github.com/open-edge-platform/image-composer-tool.git
docker build \
  -f build/ict/Dockerfile \
  --target ict \
  -t ict \
  .
docker build \
  --build-arg http_proxy \
  --build-arg https_proxy \
  --build-arg no_proxy \
  -f build/ict/Dockerfile \
  --target ict \
  -t ict \
  .
scripts/build-in-container.sh \
  ~/my-image/my-image.yml ./out
scripts/build-in-container.sh \
  ~/my-image/my-image.yml ./out
scripts/build-in-container.sh \
  ~/my-image/my-image.yml ./out \
  -- --hostname edge-01 --log-level debug
scripts/build-in-container.sh \
  ~/my-image/my-image.yml ./out \
  -- --hostname edge-01 --log-level debug
scripts/build-in-container.sh \
  --mount-point /artifacts \
  ~/my-image/my-image.yml ./out
scripts/build-in-container.sh \
  --mount-point /artifacts \
  ~/my-image/my-image.yml ./out
scripts/build-in-container.sh \
  --context ~/templates \
  ~/templates/edge/my-image.yml ./out
scripts/build-in-container.sh \
  --context ~/templates \
  ~/templates/edge/my-image.yml ./out
scripts/build-in-container.sh \
  --image image-composer-tool-ict \
  ~/my-image/my-image.yml ./out
scripts/build-in-container.sh \
  --image image-composer-tool-ict \
  ~/my-image/my-image.yml ./out

Stop, Reuse, and Clean Up#

  • Stop a build with Ctrl+C. ICT unmounts its filesystems and detaches its loop devices, and nothing is copied. Do not docker kill the container: a build killed without that cleanup can leave mounts and loop devices behind on the host.

  • Reuse the web UI’s image. If you run the web UI with docker compose, Compose built the same container image as <project>-ict, by default image-composer-tool-ict. Pass --image image-composer-tool-ict to use it.

  • Reclaim disk space taken by downloaded packages and work trees with docker volume rm ict-cache ict-workspace.

Troubleshooting#

Symptom

Fix

image 'ict' not found

Build it as in step 1, or pass the name of the one you have with --image.

A file the template references is not found

It is outside the mounted directory. Pass a --context that contains it, as in Choose What Is Mounted.

The build fails while resolving or downloading packages

The log names the repository it could not reach. Behind a proxy, check that your proxy variables are exported and that no_proxy does not send that host around the proxy.

Next Steps#