This document provides comprehensive Docker documentation for the XVM project. For basic Docker usage and installation options, see the main README.md.
The XDK Docker Container provides native multi-platform support with architecture-specific native launchers.
Just want to run XVM tools? Skip to Using Pre-built Images section.
For developers building the project, see the Building Images section.
All Docker tasks are now organized in the docker/ subproject. Run from project root:
docker:buildAmd64- Build Docker image for AMD64 platformdocker:buildArm64- Build Docker image for ARM64 platformdocker:buildAll- Build multi-platform Docker images (recommended for local builds)
docker:pushAmd64- Push AMD64 Docker image to GitHub Container Registrydocker:pushArm64- Push ARM64 Docker image to GitHub Container Registrydocker:pushAll- Build and push multi-platform Docker images (recommended for publishing)
docker:createManifest- Create multi-platform manifestdocker:testImageFunctionality- Test Docker image functionalitydocker:listImages- List Docker images in registrydocker:cleanImages- Clean up old Docker package versions with improved verification (default: keep 10 most recent, protect master images)
# Multi-platform build (recommended for local development)
./gradlew docker:buildAll
# Individual platform builds (useful for local testing)
./gradlew docker:buildAmd64 docker:buildArm64# AMD64 only
./gradlew docker:buildAmd64
# ARM64 only
./gradlew docker:buildArm64# Build and push multi-platform manifest (recommended)
./gradlew docker:pushAll
# Push individual platforms only
./gradlew docker:pushAmd64 docker:pushArm64
# Create manifest after pushing individual platforms
./gradlew docker:createManifestYou can also run tasks directly from the docker/ subdirectory:
cd docker/
../gradlew buildAll
# Or use direct Docker commands
docker buildx build --platform linux/arm64 --tag test-xvm:latest .All builds from the master branch create these tags:
ghcr.io/xtclang/xvm:latest-amd64- Latest AMD64 buildghcr.io/xtclang/xvm:latest-arm64- Latest ARM64 buildghcr.io/xtclang/xvm:VERSION-amd64- Versioned AMD64 buildghcr.io/xtclang/xvm:VERSION-arm64- Versioned ARM64 buildghcr.io/xtclang/xvm:COMMIT_SHA-amd64- Commit-specific AMD64 buildghcr.io/xtclang/xvm:COMMIT_SHA-arm64- Commit-specific ARM64 build
Multi-platform manifests:
ghcr.io/xtclang/xvm:latest- Multi-platform manifestghcr.io/xtclang/xvm:VERSION- Versioned multi-platform manifestghcr.io/xtclang/xvm:COMMIT_SHA- Commit-specific multi-platform manifest
Single Package Model: All branches use the same package with branch-based tags.
Non-master branches create these tags:
ghcr.io/xtclang/xvm:BRANCH_NAME-amd64- Branch-specific AMD64 buildghcr.io/xtclang/xvm:BRANCH_NAME-arm64- Branch-specific ARM64 buildghcr.io/xtclang/xvm:COMMIT_SHA-amd64- Commit-specific AMD64 buildghcr.io/xtclang/xvm:COMMIT_SHA-arm64- Commit-specific ARM64 build
Multi-platform manifests:
ghcr.io/xtclang/xvm:BRANCH_NAME- Branch multi-platform manifestghcr.io/xtclang/xvm:COMMIT_SHA- Commit multi-platform manifest
Example for lagergren/gradle-lifecycle-fixes:
ghcr.io/xtclang/xvm:gradle-lifecycle-fixes-amd64ghcr.io/xtclang/xvm:gradle-lifecycle-fixes-arm64ghcr.io/xtclang/xvm:gradle-lifecycle-fixes
- Uses pre-built XDK distribution ZIP (no source cloning or compilation in Docker)
DIST_ZIP_URL- Path or URL to pre-built XDK distribution ZIP file (required)JAVA_VERSION=25- Uses Bellsoft Liberica OpenJDK 25 Alpine for runtime- Platform matches host architecture (linux/amd64 on x86, linux/arm64 on ARM)
- Uses script launchers included in the distribution (xcc, xec, xtc)
The Docker build uses a single helper script in docker/scripts/:
extract-distribution.sh- Extracts the pre-built XDK distribution ZIP file
You can override default settings using build arguments:
# Use different Java version for runtime
docker buildx build --build-arg JAVA_VERSION=21 --build-arg DIST_ZIP_URL=xdk-dist.zip -t xvm:latest .
# Use distribution from URL
docker buildx build --build-arg DIST_ZIP_URL=https://example.com/xdk-dist.zip -t xvm:latest .
# Use local distribution file (must be in build context)
docker buildx build --build-arg DIST_ZIP_URL=/path/to/xdk-dist.zip -t xvm:latest .DIST_ZIP_URL(required) - Path to XDK distribution ZIP file or download URLJAVA_VERSION(default: 25) - Java version to use for runtime
If you prefer using Docker commands directly instead of Gradle tasks, you must first build the XDK distribution:
# First, create the distribution
./gradlew xdk:distZip
# Then build Docker image
cd docker
cp ../xdk/build/distributions/xdk-*.zip xdk-dist.zip
docker buildx build --build-arg DIST_ZIP_URL=xdk-dist.zip -t xvm:latest .# Force Linux AMD64
docker buildx build --platform linux/amd64 -t xvm:amd64 --load .
# Force Linux ARM64
docker buildx build --platform linux/arm64 -t xvm:arm64 --load .
# Multi-platform manifest (creates separate images for each arch)
docker buildx build --platform linux/amd64,linux/arm64 -t xvm:latest --push .- Docker builds SEPARATE images for each architecture specified
- Each platform uses the script launchers from the pre-built distribution
- Results in a manifest with architecture-specific images
- Docker automatically pulls the correct image for the runtime platform
# With GitHub Actions caching
docker buildx build --cache-from type=gha --cache-to type=gha,mode=max -t xvm:latest .
# Push to custom registry
docker buildx build --platform linux/amd64,linux/arm64 -t myregistry/xvm:latest --push .
# Extract architecture-specific launcher to host
docker buildx build --target launcher-export --platform linux/arm64 -o . .Pre-built Docker images are automatically published to GitHub Container Registry from the latest master branch.
The images are public and can be used immediately:
# Check if XVM tools work
docker run --rm ghcr.io/xtclang/xvm:latest xec --version
docker run --rm ghcr.io/xtclang/xvm:latest xcc --version
# Compile and run an Ecstasy program
echo 'module HelloWorld { void run() { @Inject Console console; console.print("Hello, World"); } }' > hello.x
docker run -v $(pwd):/workspace -w /workspace --rm ghcr.io/xtclang/xvm:latest xcc hello.x
docker run -v $(pwd):/workspace -w /workspace --rm ghcr.io/xtclang/xvm:latest xec helloAll images are multi-platform and work on AMD64 and ARM64:
# Latest build from master branch
ghcr.io/xtclang/xvm:latest
# Specific version tags
ghcr.io/xtclang/xvm:0.4.4-SNAPSHOT
# Commit-specific builds
ghcr.io/xtclang/xvm:abc1234567890abcdef1234567890abcdef12 # Full commit hash
# Branch-specific builds
ghcr.io/xtclang/xvm:master # Branch name
ghcr.io/xtclang/xvm:feature-branch # Feature branch
# Platform-specific (if needed)
ghcr.io/xtclang/xvm:latest-amd64
ghcr.io/xtclang/xvm:latest-arm64# Run xec directly (default command)
docker run --rm ghcr.io/xtclang/xvm:latest
# Run xcc compiler
docker run --rm ghcr.io/xtclang/xvm:latest xcc --help
# Compile local files
docker run -v $(pwd):/workspace -w /workspace --rm ghcr.io/xtclang/xvm:latest xcc myfile.x
# Run compiled program
docker run -v $(pwd):/workspace -w /workspace --rm ghcr.io/xtclang/xvm:latest xec MyProgram
# Check build information
docker run --rm ghcr.io/xtclang/xvm:latest cat /opt/xdk/xvm.json# View the multi-platform manifest list
docker manifest inspect ghcr.io/xtclang/xvm:latest
# Check which architecture was automatically pulled for your machine
docker image inspect ghcr.io/xtclang/xvm:latest --format '{{.Architecture}}'
# View complete image details (metadata, layers, config)
docker image inspect ghcr.io/xtclang/xvm:latest
# Get specific image information
docker image inspect ghcr.io/xtclang/xvm:latest --format '{{.Config.Env}}' # Environment variables
docker image inspect ghcr.io/xtclang/xvm:latest --format '{{.Config.Cmd}}' # Default command
docker image inspect ghcr.io/xtclang/xvm:latest --format '{{.Size}}' # Image size in bytes
docker image inspect ghcr.io/xtclang/xvm:latest --format '{{.Created}}' # Build timestampThe manifest shows available platforms (linux/amd64 and linux/arm64), and Docker automatically selects the correct architecture for your machine without requiring platform specification.
- No shell access: Images use distroless base (no bash/sh for security)
- Small size: ~101MB total (includes Java runtime + XDK)
- Script launchers: Uses the script launchers from the XDK distribution
- Public access: No
docker loginrequired for pulling images
version: '3.8'
services:
xvm-compiler:
image: ghcr.io/xtclang/xvm:latest
platform: linux/amd64 # Optional: force platform
volumes:
- ./src:/workspace
working_dir: /workspace
command: xcc main.x
xvm-runtime:
image: ghcr.io/xtclang/xvm:latest
volumes:
- ./compiled:/workspace
working_dir: /workspace
command: xec app.xtc- Docker with buildx support
- Authentication to GitHub Container Registry (
docker login ghcr.io) - For multi-platform builds: Docker buildx builder with multi-platform support
Docker images are automatically built and published when:
- Code is pushed to the
masterbranch - Manual workflow dispatch is triggered with
docker-image: true - Manual workflow dispatch is triggered with
docker-clean: true(runs cleanup only)
The CI workflow accepts these input parameters:
docker-image: Always build Docker images regardless of branchdocker-clean: Always run Docker cleanup regardless of branchsnapshot-maven: Always publish snapshot packages regardless of branchplatforms: Run only single platform (ubuntu-latest, windows-latest, or all platforms)test: Run manual tests (default: true)parallel-test: Run manual tests in parallel mode (default: true)extra-gradle-options: Extra Gradle options to pass to the build
# Build Docker images on any branch
gh workflow run commit.yml --ref your-branch-name --raw-field docker-image=true
# Test cleanup functionality only
gh workflow run commit.yml --ref your-branch-name --raw-field docker-clean=true --raw-field platforms=ubuntu-latest
# Full build with single platform for faster testing
gh workflow run commit.yml --ref your-branch-name --raw-field docker-image=true --raw-field platforms=ubuntu-latestThe GitHub Actions workflow (.github/workflows/commit.yml) performs:
-
Build Verification: Runs on Ubuntu + Windows matrix
- Uses
setup-xvm-buildaction for consistent environment setup - Builds XDK with
./gradlew installDist - Runs all tests including manual tests
- Uploads build artifacts for reuse
- Uses
-
Compute Docker Tags: Calculates metadata once for consistency
- Determines version, branch, and commit information
- Generates tag lists for all subsequent Docker operations
-
Docker Build & Push: Only after ALL tests pass
- Downloads verified build artifacts (no rebuild)
- Builds multi-platform images (AMD64 + ARM64) in parallel on native runners
- Uses pre-built XDK artifacts with fresh source checkout
- Pushes to GitHub Container Registry with multiple tags
-
Docker Testing: Validates functionality
- Tests both
xecandxcccommands - Validates compilation and execution of test programs
- Ensures native launcher functionality works correctly
- Tests both
-
Package Cleanup: Automated maintenance with enhanced verification
- Removes old Docker package versions (keeps 10 most recent + 1 master image)
- Uses retry verification with 3 attempts and 5-second delays
- Fails CI build if deletions don't complete (prevents silent failures)
After successful builds, these tags are published:
ghcr.io/xtclang/xvm:latest
ghcr.io/xtclang/xvm:latest-amd64
ghcr.io/xtclang/xvm:latest-arm64
ghcr.io/xtclang/xvm:0.4.4-SNAPSHOT
ghcr.io/xtclang/xvm:abc1234567890abcdef1234567890abcdef12 # Full commit hash
ghcr.io/xtclang/xvm:master # Branch name- Test-First: Docker builds ONLY run if all tests pass
- Multi-Platform: Single workflow builds both AMD64 and ARM64
- Fast Builds: Reuses verified artifacts, includes layer caching
- Commit Tracking: Each image tagged with git commit for traceability
- Public Access: Images are publicly accessible without authentication
Each image includes build metadata at /opt/xdk/xvm.json:
{
"buildDate": "2025-07-30T14:42:57Z",
"commit": "d5c6b7c3f8236665037f2c33731419d004195f8a",
"branch": "master",
"version": "master",
"platform": "linux/amd64"
}- Source Code: Uses pre-built XDK distribution ZIP (no source cloning in Docker)
- Multi-platform: Uses Docker buildx to create separate images per architecture
- Script Launchers: Uses the script launchers included in the distribution
- Caching: Local builds use filesystem cache, CI uses GitHub Actions cache
- Base Image: Uses Bellsoft Liberica JRE for minimal footprint
- Image Size: ~101MB (vs ~300MB+ with full JRE base images)
- Build Time: ~3-5 minutes for multi-platform (with caching)
- Startup: Native launchers provide fast startup vs pure Java
- Security: Distroless base has no shell, package managers, or extra tools
- No shell access: Images are distroless (use docker exec with external tools if debugging needed)
- Multi-platform pushes: Cannot use
--load(pushes directly to registry) - Platform emulation: Cross-platform builds may be slower on some systems
To interact with GitHub Container Registry packages (listing, deleting, etc.), you need a Personal Access Token with appropriate scopes:
# Login to GitHub Container Registry using gh CLI
gh auth login
gh auth token | docker login ghcr.io -u $(gh api user --jq .login) --password-stdinFor different operations, you need these token scopes:
- Pulling public images: No authentication required
- Pulling private images:
read:packages - Listing packages:
read:packages - Deleting packages:
read:packages+delete:packages
To add scopes to your existing GitHub CLI authentication:
gh auth refresh --hostname github.com --scopes read:packages,delete:packagesIf you're setting up Docker containerization and CI/CD for the first time, here's the complete bootstrap process:
First, authenticate with comprehensive scopes for full CI/CD operations:
# Authenticate with all necessary scopes for containerization and CI
gh auth refresh --hostname github.com --scopes repo,read:packages,write:packages,delete:packages,workflow
# Verify authentication and scopes
gh auth status --show-tokenRequired Scopes Explained:
repo- Access repository contents, create releases, manage artifactsread:packages- List and pull Docker images/packageswrite:packages- Push Docker images to GitHub Container Registrydelete:packages- Clean up old Docker image versions (for maintenance)workflow- Trigger and manage GitHub Actions workflows
Security Note: These scopes are standard for containerization workflows. The GitHub CLI stores tokens securely in your system keychain (macOS Keychain, Windows Credential Manager, Linux keyring).
# Login to GitHub Container Registry (one-time setup)
gh auth token | docker login ghcr.io -u $(gh api user --jq .login) --password-stdin
# Verify Docker registry access
docker pull ghcr.io/xtclang/xvm:latest --quiet# Verify Docker buildx multi-platform support
docker buildx version
# Create buildx builder if needed (for multi-platform builds)
docker buildx create --name multiplatform --use
docker buildx inspect --bootstrap
# Test local Docker build
./gradlew docker:buildArm64 # or buildAmd64 depending on your platformFor GitHub Actions to work properly, ensure these secrets are configured in your repository:
Repository Settings → Secrets and variables → Actions:
# Required secrets (these should already be configured)
GITHUB_TOKEN # Automatically provided by GitHub Actions
ORG_XTCLANG_GPG_SIGNING_KEY # For package signing (if enabled)
ORG_XTCLANG_GPG_SIGNING_PASSWORD # For package signing (if enabled)
# Optional secrets for enhanced publishing
ORG_XTCLANG_GRADLE_PLUGIN_PORTAL_PUBLISH_KEY # For Gradle plugin portal
ORG_XTCLANG_GRADLE_PLUGIN_PORTAL_PUBLISH_SECRET # For Gradle plugin portal
PACKAGE_DELETE_TOKEN # For automated cleanup (custom token with delete:packages)The GITHUB_TOKEN is automatically provided and has sufficient permissions for Docker operations, but lacks delete:packages scope.
For Docker Package Cleanup: The PACKAGE_DELETE_TOKEN is optional but recommended:
- Without it: Cleanup shows warnings but doesn't fail CI builds
- With it: Cleanup actually deletes old package versions
- Setup: Organization owner creates fine-grained PAT with
delete:packagesscope
# Test local builds first
./gradlew docker:buildArm64
./gradlew docker:buildArm64 # Second run should be faster (caching test)
# Test manual workflow dispatch
gh workflow run "XVM Verification and Package Updates" \
--ref your-branch-name \
--field docker-image=true \
--field platforms=all
# Monitor the workflow
gh run list --limit 3
gh run view --web # Opens in browser# Test package operations
./gradlew docker:listImages # List all Docker packages
./gradlew docker:cleanupVersions # Clean up old versions (with confirmation)
# View packages in GitHub web interface
open "https://github.com/orgs/xtclang/packages"# Clear and re-authenticate if having issues
gh auth logout --hostname github.com
gh auth login --hostname github.com --scopes repo,read:packages,write:packages,delete:packages,workflow# Re-authenticate with Docker registry
gh auth token | docker login ghcr.io -u $(gh api user --jq .login) --password-stdin
# Test with explicit package name
docker pull ghcr.io/xtclang/xvm:latest# Check Docker buildx setup
docker buildx ls
# Recreate builder if needed
docker buildx rm multiplatform
docker buildx create --name multiplatform --driver docker-container --use# Check recent workflow runs
gh run list --limit 5
# View detailed logs
gh run view WORKFLOW_ID --log
# Common issue: missing authentication
# Solution: Verify GITHUB_TOKEN has sufficient permissions in repo settingsFor enhanced security, you can use fine-grained tokens scoped to specific repositories:
-
GitHub.com → Settings → Developer settings → Personal access tokens → Fine-grained tokens
-
Select specific repositories (e.g., just
xtclang/xvm) -
Choose minimal permissions:
- Contents: Read (for repository access)
- Packages: Read + Write + Delete (for Docker operations)
- Actions: Write (for workflow dispatch)
- Metadata: Read (always required)
-
Use the token:
gh auth login --with-token < token.txtBenefits: More secure than classic tokens, repository-specific, granular permissions.
Once bootstrapped, maintain your setup with these periodic tasks:
- Monthly: Review and clean up old Docker image versions
- Quarterly: Rotate authentication tokens for security
- After major changes: Test the complete CI/CD pipeline end-to-end
- Before releases: Verify all package permissions and visibility settings
This bootstrap process ensures your containerization and CI/CD pipeline is properly configured with appropriate security and permissions.
gh api 'orgs/xtclang/packages?package_type=container' --jq '.[] | {name: .name, visibility: .visibility, updated_at: .updated_at}'gh api -X DELETE 'orgs/xtclang/packages/container/PACKAGE_NAME'gh api 'orgs/xtclang/packages/container/PACKAGE_NAME'- Main images: Use simple names like
xvmfor primary project artifacts - Branch-specific: Use
xvm-BRANCHformat for development builds (e.g.,xvm-feature-branch) - Avoid clutter: Clean up temporary or test images regularly to keep the registry organized
- Public packages: Accessible without authentication - preferred for open source
- Internal packages: Only accessible to organization members
- Private packages: Only accessible to specific users/teams
Change visibility in the GitHub web interface under package settings.
Important: GitHub Container Registry keeps ALL package versions by default - there's no built-in retention policy.
- Each
docker pushcreates a new version stored permanently - Tags like
latestget reassigned but old image data remains under SHA digests - Storage grows indefinitely with every CI build
- Manual cleanup required to prevent unbounded growth
# Count total versions stored
gh api 'orgs/xtclang/packages/container/xvm/versions' --jq 'length'
# List recent versions with creation dates
gh api 'orgs/xtclang/packages/container/xvm/versions' --jq '.[] | {id: .id, created_at: .created_at}' | head -10The XVM project uses a built-in Gradle task for automated cleanup with enhanced safety features:
# Manual cleanup (with confirmation prompts)
./gradlew docker:cleanImages
# Automated cleanup (for CI - no prompts)
./gradlew docker:cleanImages -Pforce=true
# Dry-run to see what would be deleted
./gradlew docker:cleanImages -PdryRun=true
# Custom retention count (default: 10)
./gradlew docker:cleanImages -PkeepCount=15Enhanced Safety Features:
- Always protects at least one master/release image
- Keeps configurable number of most recent versions (default: 10)
- Uses retry verification with 3 attempts and 5-second delays
- Fails in CI if deletions don't complete (prevents silent failures)
- Detailed logging shows exactly what was deleted vs kept
You can also use external tools for more complex retention policies:
- name: Clean up old container versions
uses: snok/container-retention-policy@v3
with:
image-names: xvm
cut-off: 4 weeks ago UTC
keep-n-most-recent: 10
account-type: org
org-name: xtclang
token: ${{ secrets.PACKAGE_DELETE_TOKEN }}# List all versions with details
./gradlew docker:listImages
# Delete specific version by ID (use with caution)
gh api -X DELETE 'orgs/xtclang/packages/container/xvm/versions/VERSION_ID'Note: The built-in cleanup task is safer and more reliable than manual deletion scripts, with comprehensive verification to prevent accidental data loss.