Docker Support
Terminology: This specification uses RFC 2119 keywords (MUST, SHOULD, MAY, etc.) to indicate requirement levels.
This document describes Structyl's Docker integration.
Non-Goals
This specification does not cover:
- Container orchestration: Structyl uses Docker for builds only, not production deployment.
- Multi-stage builds: Generated Dockerfiles are for development/CI, not optimized production images.
- Docker Swarm/Kubernetes: Only Docker Compose is supported; orchestrators are out of scope.
- Image publishing: Building and pushing images to registries is not managed by Structyl.
Overview
Structyl supports running builds inside Docker containers, providing:
- Reproducible builds across environments
- No need to install language toolchains locally
- Isolation between different language runtimes
- CI/local build parity
Requirements
| Component | Minimum Version | Notes |
|---|---|---|
| Docker Engine | 20.10+ | Required for --user mapping on Unix systems |
| Docker Compose | v2.0+ | Uses docker compose (v2) syntax |
Older versions MAY work but are not tested. Structyl uses docker compose (v2) commands, not the legacy docker-compose (v1) binary.
Enabling Docker Mode
Command Line Flag
structyl build cs --docker
structyl test --docker
structyl ci --dockerEnvironment Variable
export STRUCTYL_DOCKER=1
structyl build # Runs in DockerOr per-command:
STRUCTYL_DOCKER=1 structyl buildActivation Precedence
| Condition | Docker Mode |
|---|---|
--docker flag present | Enabled |
--no-docker flag present | Disabled (overrides env var) |
STRUCTYL_DOCKER = 1, true, yes (case-insensitive) | Enabled |
STRUCTYL_DOCKER = any other non-empty value | Disabled |
STRUCTYL_DOCKER unset or empty | Disabled |
Command-line flags take precedence over environment variables. The --no-docker flag explicitly disables Docker mode even if STRUCTYL_DOCKER is set.
Note: The
STRUCTYL_DOCKERenvironment variable uses an allow-list approach: only1,true, andyes(case-insensitive) enable Docker mode. All other non-empty values (including0,false,no) silently disable Docker mode.
Docker Compose Configuration
Structyl uses Docker Compose to manage containers. The compose file can be:
- Auto-generated by Structyl (default)
- User-provided for custom configuration
Auto-Generated Compose File
If no docker-compose.yml exists, Structyl generates one based on .structyl/config.json:
# Auto-generated by Structyl
services:
cs:
build:
context: .
dockerfile: cs/Dockerfile
volumes:
- ./cs:/workspace/cs
- ./tests:/workspace/tests:ro
working_dir: /workspace/cs
py:
build:
context: .
dockerfile: py/Dockerfile
volumes:
- ./py:/workspace/py
- ./tests:/workspace/tests:ro
working_dir: /workspace/pyUser-Provided Compose File
Place a docker-compose.yml at project root. Structyl will use it instead of generating one.
Dockerfile Templates
Each target needs a Dockerfile. Options:
1. Built-in Templates (Default)
Structyl provides default Dockerfiles for common languages. Base image versions are determined by the toolchain's mise configuration. See toolchains.md for current defaults.
| Language | Base Image Pattern |
|---|---|
| C# | mcr.microsoft.com/dotnet/sdk |
| Go | golang |
| Kotlin | gradle |
| Python | python |
| R | rocker/verse |
| Rust | rust |
| TypeScript | node |
Structyl generates Dockerfiles using FROM <base-image>:<version> where the version is derived from the toolchain configuration.
2. Custom Dockerfile
Place a Dockerfile in the target directory:
cs/
├── Dockerfile # Custom Dockerfile
└── MyProject.csprojCustom Dockerfile takes precedence over built-in template.
3. Configuration Override
Specify in .structyl/config.json:
{
"docker": {
"services": {
"cs": {
"base_image": "mcr.microsoft.com/dotnet/sdk:9.0",
"dockerfile": "docker/cs.Dockerfile"
}
}
}
}Volume Mounts
Standard volume mounts:
| Mount | Purpose |
|---|---|
./<target>:/workspace/<target> | Target source code (read-write) |
./tests:/workspace/tests:ro | Test data (read-only) |
./.structyl/config.json:/workspace/.structyl/config.json:ro | Configuration (read-only) |
Cache Volumes
To avoid permission conflicts, use separate cache directories for Docker:
services:
cs:
volumes:
- ./cs/.nuget-docker:/tmp/.nuget
rs:
volumes:
- ./rs/.cargo-docker:/tmp/.cargoAdd to .gitignore:
**/.nuget-docker
**/.cargo-docker
**/.cache-dockerPlatform Considerations
ARM64 (Apple Silicon)
Some images don't support ARM64. Specify platform explicitly:
{
"docker": {
"services": {
"r": { "platform": "linux/amd64" },
"pdf": { "platform": "linux/amd64" }
}
}
}Docker Desktop uses Rosetta for emulation.
User Mapping
Structyl maps the container user to avoid root-owned files on the host.
| Platform | Behavior |
|---|---|
| Linux | Run as host user: --user "$(id -u):$(id -g)" |
| macOS | Run as host user: --user "$(id -u):$(id -g)" |
| Windows | Run as default container user (user mapping not supported) |
On Unix systems, the equivalent command is:
docker compose run --rm --user "$(id -u):$(id -g)" cs bash -c "dotnet build"On Windows, user mapping is omitted:
docker compose run --rm cs powershell -Command "dotnet build"Note: On Windows, files created in containers may be owned by root. Use separate cache directories (see "Cache Volumes" above) to mitigate permission issues.
Docker Commands
Build Images
structyl docker-build # Build all images
structyl docker-build cs py # Build specific imagesExit codes:
| Code | Condition |
|---|---|
| 0 | Images built |
| 1 | Build failed |
| 3 | Docker unavailable |
Clean Docker Resources
structyl docker-clean # Remove containers, images, volumesExit codes:
| Code | Condition |
|---|---|
| 0 | Clean completed |
| 1 | Clean failed |
| 3 | Docker unavailable |
Note: Exit code 3 indicates Docker daemon is not running or Docker is not installed. See error-handling.md for the complete exit code reference.
Example Dockerfiles
Note: The Docker image versions shown below (
:8.0,:3.12-slim,:1.75,:1.23) are illustrative examples. Actual versions are derived from your toolchain configuration in.structyl/config.json. See toolchains.md for how tool versions are resolved.
C# (.NET)
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /workspace/cs
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
ENV NUGET_PACKAGES=/tmp/.nugetPython
FROM python:3.12-slim
WORKDIR /workspace/py
RUN pip install --upgrade pipRust
FROM rust:1.75
WORKDIR /workspace/rs
ENV CARGO_HOME=/tmp/.cargoGo
FROM golang:1.23
WORKDIR /workspace/go
ENV GOCACHE=/tmp/.cache
ENV GOPATH=/tmp/goservices vs targets Configuration
The Docker configuration has two per-target sections with distinct purposes:
| Section | Purpose | Use For |
|---|---|---|
services | Image building | base_image, dockerfile, platform, build-time volumes |
targets | Container runtime | platform, cache_volume, entrypoint, environment |
Platform Precedence
Both services.<target>.platform and targets.<target>.platform configure the target platform, but at different stages:
| Configuration | Effect |
|---|---|
services.<target>.platform | Platform for docker build (baked into the image) |
targets.<target>.platform | Platform for docker compose run (overrides at runtime) |
Precedence rules:
- If only
services.platformis set: The image is built for that platform and runs on that platform. - If only
targets.platformis set: The image is built for the host platform, but containers are run on the specified platform (emulated if necessary). - If both are set: The image is built with
services.platform, but runtime usestargets.platform. This configuration is typically a mistake — if platforms differ, Docker will pull/emulate the wrong architecture.
Recommendation: Set platform in services for build-time platform selection. Only use targets.platform when you need runtime-only platform override (rare).
Note: Structyl does not validate platform consistency between
servicesandtargets. Users SHOULD ensure platforms match when both are configured for the same target.
Configuration Reference
{
"docker": {
"compose_file": "docker-compose.yml",
"env_var": "STRUCTYL_DOCKER",
"services": {
"<target>": {
"base_image": "image:tag"
}
},
"targets": {
"<target>": {
"platform": "linux/amd64",
"cache_volume": "/tmp/.cache",
"entrypoint": "/bin/bash",
"environment": {
"CI": "true"
}
}
}
}
}Top-Level Fields
| Field | Description | Default |
|---|---|---|
compose_file | Path to compose file | docker-compose.yml |
env_var | Env var to enable Docker mode | STRUCTYL_DOCKER |
services | Service base image overrides | {} |
targets | Target-specific Docker config | {} |
services.<target>
| Field | Description | Default |
|---|---|---|
base_image | Base Docker image | Language-specific |
dockerfile | Custom Dockerfile path | None |
platform | Target platform for build | Host platform |
volumes | Additional volume mounts | [] |
targets.<target>
| Field | Description | Default |
|---|---|---|
platform | Target platform | Host platform |
cache_volume | Volume for build cache | None |
entrypoint | Container entrypoint override | None |
environment | Additional environment vars | {} |
Troubleshooting
Permission Denied
If files are owned by root after Docker build:
- Ensure user mapping is enabled
- Use separate cache directories (e.g.,
.nuget-docker)
Image Not Found
structyl docker-build # Rebuild all imagesSlow Builds on Apple Silicon
ARM64 emulation is slow. Options:
- Use native ARM64 images where available
- Accept slower builds for x64-only tools
- Use native builds for development, Docker for CI only