Skip to content

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

ComponentMinimum VersionNotes
Docker Engine20.10+Required for --user mapping on Unix systems
Docker Composev2.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

bash
structyl build cs --docker
structyl test --docker
structyl ci --docker

Environment Variable

bash
export STRUCTYL_DOCKER=1
structyl build  # Runs in Docker

Or per-command:

bash
STRUCTYL_DOCKER=1 structyl build

Activation Precedence

ConditionDocker Mode
--docker flag presentEnabled
--no-docker flag presentDisabled (overrides env var)
STRUCTYL_DOCKER = 1, true, yes (case-insensitive)Enabled
STRUCTYL_DOCKER = any other non-empty valueDisabled
STRUCTYL_DOCKER unset or emptyDisabled

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_DOCKER environment variable uses an allow-list approach: only 1, true, and yes (case-insensitive) enable Docker mode. All other non-empty values (including 0, false, no) silently disable Docker mode.

Docker Compose Configuration

Structyl uses Docker Compose to manage containers. The compose file can be:

  1. Auto-generated by Structyl (default)
  2. User-provided for custom configuration

Auto-Generated Compose File

If no docker-compose.yml exists, Structyl generates one based on .structyl/config.json:

yaml
# 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/py

User-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.

LanguageBase Image Pattern
C#mcr.microsoft.com/dotnet/sdk
Gogolang
Kotlingradle
Pythonpython
Rrocker/verse
Rustrust
TypeScriptnode

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.csproj

Custom Dockerfile takes precedence over built-in template.

3. Configuration Override

Specify in .structyl/config.json:

json
{
  "docker": {
    "services": {
      "cs": {
        "base_image": "mcr.microsoft.com/dotnet/sdk:9.0",
        "dockerfile": "docker/cs.Dockerfile"
      }
    }
  }
}

Volume Mounts

Standard volume mounts:

MountPurpose
./<target>:/workspace/<target>Target source code (read-write)
./tests:/workspace/tests:roTest data (read-only)
./.structyl/config.json:/workspace/.structyl/config.json:roConfiguration (read-only)

Cache Volumes

To avoid permission conflicts, use separate cache directories for Docker:

yaml
services:
  cs:
    volumes:
      - ./cs/.nuget-docker:/tmp/.nuget
  rs:
    volumes:
      - ./rs/.cargo-docker:/tmp/.cargo

Add to .gitignore:

**/.nuget-docker
**/.cargo-docker
**/.cache-docker

Platform Considerations

ARM64 (Apple Silicon)

Some images don't support ARM64. Specify platform explicitly:

json
{
  "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.

PlatformBehavior
LinuxRun as host user: --user "$(id -u):$(id -g)"
macOSRun as host user: --user "$(id -u):$(id -g)"
WindowsRun as default container user (user mapping not supported)

On Unix systems, the equivalent command is:

bash
docker compose run --rm --user "$(id -u):$(id -g)" cs bash -c "dotnet build"

On Windows, user mapping is omitted:

powershell
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

bash
structyl docker-build        # Build all images
structyl docker-build cs py  # Build specific images

Exit codes:

CodeCondition
0Images built
1Build failed
3Docker unavailable

Clean Docker Resources

bash
structyl docker-clean        # Remove containers, images, volumes

Exit codes:

CodeCondition
0Clean completed
1Clean failed
3Docker 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)

dockerfile
FROM mcr.microsoft.com/dotnet/sdk:8.0

WORKDIR /workspace/cs
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
ENV NUGET_PACKAGES=/tmp/.nuget

Python

dockerfile
FROM python:3.12-slim

WORKDIR /workspace/py
RUN pip install --upgrade pip

Rust

dockerfile
FROM rust:1.75

WORKDIR /workspace/rs
ENV CARGO_HOME=/tmp/.cargo

Go

dockerfile
FROM golang:1.23

WORKDIR /workspace/go
ENV GOCACHE=/tmp/.cache
ENV GOPATH=/tmp/go

services vs targets Configuration

The Docker configuration has two per-target sections with distinct purposes:

SectionPurposeUse For
servicesImage buildingbase_image, dockerfile, platform, build-time volumes
targetsContainer runtimeplatform, cache_volume, entrypoint, environment

Platform Precedence

Both services.<target>.platform and targets.<target>.platform configure the target platform, but at different stages:

ConfigurationEffect
services.<target>.platformPlatform for docker build (baked into the image)
targets.<target>.platformPlatform for docker compose run (overrides at runtime)

Precedence rules:

  1. If only services.platform is set: The image is built for that platform and runs on that platform.
  2. If only targets.platform is set: The image is built for the host platform, but containers are run on the specified platform (emulated if necessary).
  3. If both are set: The image is built with services.platform, but runtime uses targets.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 services and targets. Users SHOULD ensure platforms match when both are configured for the same target.

Configuration Reference

json
{
  "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

FieldDescriptionDefault
compose_filePath to compose filedocker-compose.yml
env_varEnv var to enable Docker modeSTRUCTYL_DOCKER
servicesService base image overrides{}
targetsTarget-specific Docker config{}

services.<target>

FieldDescriptionDefault
base_imageBase Docker imageLanguage-specific
dockerfileCustom Dockerfile pathNone
platformTarget platform for buildHost platform
volumesAdditional volume mounts[]

targets.<target>

FieldDescriptionDefault
platformTarget platformHost platform
cache_volumeVolume for build cacheNone
entrypointContainer entrypoint overrideNone
environmentAdditional environment vars{}

Troubleshooting

Permission Denied

If files are owned by root after Docker build:

  1. Ensure user mapping is enabled
  2. Use separate cache directories (e.g., .nuget-docker)

Image Not Found

bash
structyl docker-build  # Rebuild all images

Slow Builds on Apple Silicon

ARM64 emulation is slow. Options:

  1. Use native ARM64 images where available
  2. Accept slower builds for x64-only tools
  3. Use native builds for development, Docker for CI only

© 2026 Andrey Akinshin MIT