Skip to main content
Version: 0.4.0

Container Runtime

Linux Containers Only

The container runtime only supports Debian-based Linux container images (e.g., debian:stable-slim).

NOT supported:

  • Alpine-based images — musl-based environments have subtle behavioral differences that reduce runtime reliability. Only images with exact name alpine or ending with /alpine (after stripping tags/digests) are rejected. Images like go-alpine-builder or myorg/alpine-tools are NOT rejected.
  • Windows container images (mcr.microsoft.com/windows/*) — No POSIX shell available

Platform requirements:

  • Linux: Works natively with Docker or Podman
  • macOS: Works with Docker Desktop (uses Linux VMs internally)
  • Windows: Requires Docker Desktop with WSL2 backend in Linux containers mode

Scripts are executed using /bin/sh inside the container, which requires a standard POSIX-compatible shell environment.

The container runtime executes commands inside a Docker or Podman container. It provides complete isolation and reproducibility - your command runs in the exact same environment every time.

How It Works

When you run a command with the container runtime, Invowk™:

  1. Pulls or builds the container image (if needed)
  2. Mounts the invowkfile's directory into the container
  3. Executes the script inside the container
  4. Streams output back to your terminal

Basic Usage

{
name: "build"
implementations: [
{
script: "go build -o /workspace/bin/app ./..."
runtimes: [{
name: "container"
image: "golang:1.26"
}]
platforms: [{name: "linux"}]
}
]
}
invowk cmd myproject build

Container Image Sources

You must specify either an image or a containerfile - they're mutually exclusive.

Pre-built Images

runtimes: [{
name: "container"
image: "golang:1.26"
}]

Common images:

  • debian:stable-slim - Minimal Debian (recommended base image)
  • golang:1.26 - Go development
  • node:20 - Node.js development
  • python:3-slim - Python development

Custom Containerfile

Build from a local Containerfile/Dockerfile:

runtimes: [{
name: "container"
containerfile: "./Containerfile" // Relative to invowkfile
}]

Example Containerfile:

FROM golang:1.26

RUN apt-get update && apt-get install -y
make
git

WORKDIR /workspace

Volume Mounts

Mount additional directories into the container:

runtimes: [{
name: "container"
image: "golang:1.26"
volumes: [
"./data:/data", // Relative path
"/tmp:/tmp:ro", // Absolute path, read-only
"${HOME}/.cache:/cache" // Environment variable
]
}]

The invowkfile's directory is automatically mounted to /workspace.

Port Mappings

Expose container ports to the host:

runtimes: [{
name: "container"
image: "node:20"
ports: [
"3000:3000", // Host:Container
"8080:80" // Map container port 80 to host port 8080
]
}]

Using Interpreters

Like native runtime, containers support custom interpreters:

Auto-Detection from Shebang

{
name: "analyze"
implementations: [{
platforms: [{name: "linux"}]
script: """
#!/usr/bin/env python3
import sys
print(f"Python {sys.version} in container!")
"""
runtimes: [{
name: "container"
image: "python:3-slim"
}]
}]
}

Explicit Interpreter

{
name: "analyze"
implementations: [{
script: """
import sys
print(f"Running on Python {sys.version_info.major}")
"""
runtimes: [{
name: "container"
image: "python:3-slim"
interpreter: "python3"
}]
platforms: [{name: "linux"}]
}]
}

Environment Variables

Environment variables are passed into the container:

{
name: "deploy"
env: {
vars: {
DEPLOY_ENV: "production"
API_URL: "https://api.example.com"
}
}
implementations: [{
platforms: [{name: "linux"}]
script: """
echo "Deploying to $DEPLOY_ENV"
echo "API: $API_URL"
"""
runtimes: [{
name: "container"
image: "debian:stable-slim"
}]
}]
}

Host SSH Access

Sometimes your container needs to execute commands on the host system. Enable SSH access back to the host:

{
name: "deploy from container"
implementations: [{
script: """
# Connection credentials are provided via environment variables
echo "SSH Host: $INVOWK_SSH_HOST"
echo "SSH Port: $INVOWK_SSH_PORT"

# Connect back to host
sshpass -p $INVOWK_SSH_TOKEN ssh -o StrictHostKeyChecking=no
$INVOWK_SSH_USER@$INVOWK_SSH_HOST -p $INVOWK_SSH_PORT
'echo "Hello from host!"'
"""
runtimes: [{
name: "container"
image: "debian:stable-slim"
enable_host_ssh: true // Enable SSH server
}]
platforms: [{name: "linux"}]
}]
}

SSH Environment Variables

When enable_host_ssh: true, these variables are available:

VariableDescription
INVOWK_SSH_ENABLEDSet to true when SSH is active
INVOWK_SSH_HOSTHost address (e.g., host.docker.internal)
INVOWK_SSH_PORTSSH server port
INVOWK_SSH_USERUsername (invowk)
INVOWK_SSH_TOKENOne-time authentication token

Security

  • Each command execution gets a unique token
  • Tokens are revoked when the command completes
  • The SSH server only accepts token-based authentication
  • The server shuts down after command execution

Container Requirements

Your container needs sshpass or similar for password-based SSH:

FROM debian:stable-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client sshpass \
&& rm -rf /var/lib/apt/lists/*

Dependencies

Container dependencies are validated inside the container:

{
name: "build"
depends_on: {
tools: [
// Checked inside the container, not on host
{alternatives: ["go"]},
{alternatives: ["make"]}
]
filepaths: [
// Paths relative to container's /workspace
{alternatives: ["go.mod"]}
]
}
implementations: [{
platforms: [{name: "linux"}]
script: "make build"
runtimes: [{
name: "container"
image: "golang:1.26"
}]
}]
}

Container Engine

Invowk supports both Docker and Podman. Configure your preference:

// ~/.config/invowk/config.cue
container_engine: "podman" // or "docker"

If not configured, Invowk tries:

  1. podman (if available)
  2. docker (fallback)

Working Directory

By default, the invowkfile's directory is mounted to /workspace and used as the working directory:

{
name: "build"
implementations: [{
platforms: [{name: "linux"}]
script: """
pwd # Outputs: /workspace
ls # Shows your project files
"""
runtimes: [{
name: "container"
image: "debian:stable-slim"
}]
}]
}

Override with workdir:

{
name: "build frontend"
workdir: "./frontend" // Mounted and used as workdir
implementations: [{
platforms: [{name: "linux"}]
script: "npm run build"
runtimes: [{
name: "container"
image: "node:20"
}]
}]
}

Complete Example

Here's a full-featured container command:

{
name: "build and test"
description: "Build and test in isolated container"
env: {
vars: {
GO_ENV: "test"
CGO_ENABLED: "0"
}
}
depends_on: {
tools: [{alternatives: ["go"]}]
filepaths: [{alternatives: ["go.mod"]}]
}
implementations: [{
script: """
echo "Go version: $(go version)"
echo "Building..."
go build -o /workspace/bin/app ./...
echo "Testing..."
go test -v ./...
echo "Done!"
"""
runtimes: [{
name: "container"
image: "golang:1.26"
volumes: [
"${HOME}/go/pkg/mod:/go/pkg/mod:ro" // Cache Go dependencies
]
}]
platforms: [{name: "linux"}, {name: "macos"}]
}]
}

Auto-provisioning

When container.auto_provision.enabled is true (default), Invowk builds a cached, derived image by attaching a small provisioned layer on top of your base image (either the image you specify or the image built from your Containerfile/Dockerfile). That layer includes the invowk binary and any modules, so invowk commands are available inside the container.

Auto-provisioning runs for every container execution, not just interactive mode. The derived image is cached as invowk-provisioned:<hash> and reused; if provisioning fails, Invowk warns and runs the base image instead.

Interactive Mode

Container runtime fully supports interactive mode (-i). Interactive runs use the same provisioned image layer described above (when enabled) and add host-side TUI plumbing, enabling:

  • TUI components as modal overlays
  • Full PTY support for password prompts and confirmations
  • Seamless integration with host terminal
# Run a container command interactively
invowk cmd myproject build -i -r container

When you run with -i, Invowk:

  1. Uses the provisioned image layer (if enabled)
  2. Starts a TUI server on the host
  3. Forwards TUI requests from container to host
  4. Renders overlays on your terminal

This means your scripts can use invowk tui commands inside containers:

{
name: "deploy container"
implementations: [{
platforms: [{name: "linux"}]
script: """
# This TUI confirm appears as an overlay on your terminal
if invowk tui confirm "Deploy to production?"; then
echo "Deploying..."
./deploy.sh
fi
"""
runtimes: [{
name: "container"
image: "debian:stable-slim"
}]
}]
}
tip

The provisioned image is cached as invowk-provisioned:<hash>, so subsequent runs are fast.

Advantages

  • Reproducibility: Same environment everywhere
  • Isolation: No host system pollution
  • Version control: Pin exact tool versions
  • CI/CD parity: Local builds match CI builds
  • Clean builds: Fresh environment each time

Limitations

  • Performance: Container startup overhead
  • Disk space: Images consume storage
  • Complexity: Need to manage images
  • Host access: Limited without SSH bridge

When to Use Container

  • Reproducible builds: When consistency matters
  • CI/CD pipelines: Match local and CI environments
  • Legacy projects: Isolate old tool versions
  • Team onboarding: No local tool installation needed
  • Clean-room builds: Test without host pollution

Troubleshooting

Container Not Starting

# Check if container engine is available
docker --version # or: podman --version

# Check if image exists
docker images | grep golang

Slow First Run

The first run pulls the image. Subsequent runs are faster:

# Pre-pull images
docker pull golang:1.26
docker pull node:20

Permission Issues

On Linux, you may need to configure container permissions:

# For Docker
sudo usermod -aG docker $USER

# For Podman (rootless)
# Usually works out of the box

Next Steps