iso is a CLI tool that provides isolated Docker environments for running commands and tests. It manages Docker containers automatically, handling image builds, service dependencies, and container lifecycle.
Note: ISO is an internal tool built by the Miren team to solve our own development environment needs. We're sharing it publicly to allow others to contribute to the Miren Runtime (which uses ISO for development and tests), and in case others find it useful. See CONTRIBUTING.md for more context on the project's scope.
- Service Support: Define additional services (databases, caches, etc.) in
.iso/services.yml - Automatic Container Management: Detects if a container is already running and reuses it
- Automatic Image Building: Builds Docker images from Dockerfiles when needed
- Pre/Post-run Hooks: Execute setup and teardown scripts automatically
- Service Readiness Checks: Wait for services to be ready before running commands
- Simple CLI Interface: Clean command-line interface for common tasks
- Workspace Mounting: Automatically mounts the current directory into the container at
/workspace - Exit Code Mirroring: Mirrors container exit codes for proper CI/CD integration
- Cross-platform: Works on macOS, Windows, and Linux with embedded Linux binaries
This project uses quake as its build tool. Install it first:
go install miren.dev/quake@latest# Build using quake
quake build
# Install to ~/bin
quake install
# Or build directly
go build -o bin/iso ./cmd/isoOr install via go:
go install miren.dev/iso/cmd/iso@latestIf you use Nix, you can install or run iso directly from the flake:
# Run without installing
nix run github:mirendev/iso -- run go test ./...
# Install to your profile
nix profile install github:mirendev/iso
# Add to your flake.nix as an input
inputs.iso.url = "github:mirendev/iso";
# Use in a dev shell
nix develop github:mirendev/isoThe Nix build produces static binaries with embedded portable Linux binaries for container use.
go get miren.dev/isoRun a command in the isolated environment:
./iso run [command] [args...]Example:
./iso run go test ./...
./iso run make build
./iso run echo "Hello from iso"
./iso run ls -la
./iso run grep -r "pattern" .The first time you run a command, iso will:
- Build the Docker image from your Dockerfile (if not already built)
- Start any services defined in services.yml
- Start the main container
- Execute your command inside the container
Subsequent runs will reuse the existing container for faster execution.
Build or rebuild the Docker image:
./iso buildForce a rebuild:
./iso build --rebuildView the current status of the image and container:
./iso statusStop and remove the container and services (image is preserved):
./iso stopCreate a .iso/ directory in your project root:
myproject/
├── .iso/
│ ├── Dockerfile # Main container definition
│ ├── services.yml # Additional services (optional)
│ ├── pre-run.sh # Pre-run hook (optional)
│ └── post-run.sh # Post-run hook (optional)
├── src/
└── ...
Create a .iso/Dockerfile in your project. Example:
FROM golang:1.23-alpine
RUN apk add --no-cache \
git \
make \
bash \
curl
WORKDIR /workspaceThe tool will automatically mount your current directory to /workspace in the container.
Create a .iso/services.yml file to define additional services:
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: testdb
MYSQL_USER: testuser
MYSQL_PASSWORD: testpass
port: 3306 # Optional: ISO will wait for this port to be ready
redis:
image: redis:7-alpine
port: 6379Services are accessible by their service name (e.g., mysql, redis) from the main container.
Create optional hook scripts in .iso/:
.iso/pre-run.sh - Runs before each command:
#!/bin/bash
echo "Setting up environment..."
# Database migrations, cache warming, etc..iso/post-run.sh - Runs after each command:
#!/bin/bash
echo "Cleaning up..."
# Cleanup temporary files, reset database, etc.- Container Detection: Checks if a container with the specified name is already running
- Image Building: If no image exists, builds it from the Dockerfile
- Service Management: Starts and monitors services defined in services.yml
- Container Lifecycle:
- If container is running: executes command directly inside it
- If container exists but stopped: starts it and executes command
- If container doesn't exist: creates a new one and executes command
- Command Execution: Uses the embedded Linux binary inside the container to handle pre/post hooks
# Check status (nothing exists yet)
./iso status
# Run tests (builds image, starts services, creates container, runs tests)
./iso run go test ./...
# Run another command (reuses existing container)
./iso run go build
# List files in the workspace
./iso run ls -la
# Check status (shows running container and services)
./iso status
# Stop everything when done
./iso stoppackage main
import (
"fmt"
"log"
"miren.dev/iso"
)
func main() {
// Create a new ISO client
client, err := iso.New(iso.Options{
IsoDir: ".iso", // Directory containing Dockerfile and services.yml
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Run a command
exitCode, err := client.Run([]string{"go", "test", "./..."})
if err != nil {
log.Fatal(err)
}
if exitCode != 0 {
log.Fatalf("Command failed with exit code %d", exitCode)
}
// Check status
status, err := client.Status()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Image exists: %v\n", status.ImageExists)
fmt.Printf("Container state: %s\n", status.ContainerState)
}iso/
├── iso.go # Public API
├── docker.go # Docker client wrapper (internal)
├── container.go # Container management (internal)
├── services.go # Service management (internal)
├── embedded_binary.go # Cross-platform binary embedding
├── cmd/
│ └── iso/
│ └── main.go # CLI implementation
├── testdata/ # Example MySQL integration test
├── Quakefile # Build configuration
├── go.mod
└── README.md
- Docker installed and running
- Go 1.21 or later (for building)
- Docker daemon accessible (typically via
/var/run/docker.sock)
Apache License 2.0 - see LICENSE for details.