diff --git a/containers/agent/Dockerfile b/containers/agent/Dockerfile index 216c817d..1a9c16dd 100644 --- a/containers/agent/Dockerfile +++ b/containers/agent/Dockerfile @@ -1,26 +1,40 @@ -FROM ubuntu:22.04 +# BASE_IMAGE allows customization of the base Ubuntu image for closer parity +# with GitHub Actions runner environments. Options: +# - ubuntu:22.04 (default): Minimal image, smallest size (~200MB) +# - ghcr.io/catthehacker/ubuntu:runner-22.04: Closer to GitHub Actions runner (~2-5GB) +# - ghcr.io/catthehacker/ubuntu:full-22.04: Near-identical to GitHub Actions runner (~20GB compressed) +# Use --build-arg BASE_IMAGE= to customize +ARG BASE_IMAGE=ubuntu:22.04 +FROM ${BASE_IMAGE} # Install required packages and Node.js 22 +# Note: Some packages may already exist in runner-like base images, apt handles this gracefully RUN apt-get update && \ apt-get install -y --no-install-recommends \ iptables \ curl \ ca-certificates \ git \ + gh \ gnupg \ dnsutils \ net-tools \ netcat-openbsd \ gosu \ libcap2-bin && \ + # Prefer system binaries over runner toolcache (e.g., act images) for Node checks. + export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH" && \ # Install Node.js 22 from NodeSource - # Remove any existing nodejs packages first to avoid conflicts - apt-get remove -y nodejs npm || true && \ - curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ - apt-get install -y nodejs && \ - # Verify Node.js 22 was installed correctly - node --version | grep -q "^v22\." || (echo "ERROR: Node.js 22 not installed correctly" && exit 1) && \ - npx --version || (echo "ERROR: npx not found" && exit 1) && \ + # Check if Node.js 22 is already installed (common in runner images) + if ! command -v node >/dev/null 2>&1 || ! node --version | grep -qE '^v22\.'; then \ + # Remove any existing nodejs packages first to avoid conflicts + apt-get remove -y nodejs npm || true && \ + curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ + apt-get install -y nodejs && \ + # Verify Node.js 22 was installed correctly + node --version | grep -q "^v22\." || (echo "ERROR: Node.js 22 not installed correctly" && exit 1) && \ + npx --version || (echo "ERROR: npx not found" && exit 1); \ + fi && \ rm -rf /var/lib/apt/lists/* # Create non-root user with UID/GID matching host user @@ -28,8 +42,20 @@ RUN apt-get update && \ # and prevents file ownership issues with mounted volumes ARG USER_UID=1000 ARG USER_GID=1000 -RUN groupadd -g ${USER_GID} awfuser && \ - useradd -u ${USER_UID} -g ${USER_GID} -m -s /bin/bash awfuser && \ +RUN if ! getent group awfuser >/dev/null 2>&1; then \ + if ! getent group ${USER_GID} >/dev/null 2>&1; then \ + groupadd -g ${USER_GID} awfuser; \ + else \ + groupadd awfuser; \ + fi; \ + fi && \ + if ! id -u awfuser >/dev/null 2>&1; then \ + if ! getent passwd ${USER_UID} >/dev/null 2>&1; then \ + useradd -u ${USER_UID} -g awfuser -m -s /bin/bash awfuser; \ + else \ + useradd -g awfuser -m -s /bin/bash awfuser; \ + fi; \ + fi && \ # Create directories for awfuser mkdir -p /home/awfuser/.copilot/logs && \ chown -R awfuser:awfuser /home/awfuser diff --git a/docs/usage.md b/docs/usage.md index 768804ab..bca28fc5 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -25,6 +25,9 @@ Options: --env-all Pass all host environment variables to container -v, --mount Volume mount (host_path:container_path[:ro|rw]) --tty Allocate a pseudo-TTY for interactive tools + --build-local Build containers locally instead of using GHCR images + --agent-base-image Base image for agent container (requires --build-local) + See "Agent Base Image" section for available options -V, --version Output the version number -h, --help Display help for command @@ -342,6 +345,102 @@ SSL Bump requires intercepting HTTPS traffic: For more details, see [SSL Bump documentation](ssl-bump.md). +## Agent Base Image (GitHub Actions Parity) + +By default, the agent container uses `ubuntu:22.04`, a minimal image optimized for size (~200MB). When you need closer parity with GitHub Actions runner environments, you can specify an alternative base image. + +### Available Base Images + +| Image | Size | Description | +|-------|------|-------------| +| `ubuntu:22.04` (default) | ~200MB | Minimal Ubuntu, smallest footprint | +| `ghcr.io/catthehacker/ubuntu:runner-22.04` | ~2-5GB | Medium image with common tools, closer to GitHub Actions | +| `ghcr.io/catthehacker/ubuntu:full-22.04` | ~20GB compressed | Near-identical to GitHub Actions runner | + +### Usage + +The `--agent-base-image` flag requires `--build-local` since it customizes the container build: + +```bash +# Use runner image for better GitHub Actions compatibility +sudo awf \ + --build-local \ + --agent-base-image ghcr.io/catthehacker/ubuntu:runner-22.04 \ + --allow-domains github.com \ + -- your-command + +# Use full image for maximum parity (large download, ~20GB) +sudo awf \ + --build-local \ + --agent-base-image ghcr.io/catthehacker/ubuntu:full-22.04 \ + --allow-domains github.com \ + -- your-command +``` + +### When to Use Custom Base Images + +**Use `ubuntu:22.04` (default) when:** +- Fast startup time is important +- Minimal container size is preferred +- Your commands only need basic tools (curl, git, Node.js, Docker CLI) + +**Use `runner-22.04` when:** +- You need tools commonly available in GitHub Actions (multiple Python versions, Go, Java, etc.) +- Commands fail due to missing dependencies +- Moderate GitHub Actions parity is needed + +**Use `full-22.04` when:** +- Maximum GitHub Actions parity is required +- You need specific tools only available in the full runner image +- Download time and disk space are not concerns + +### Security Considerations + +**⚠️ IMPORTANT:** Custom base images introduce supply chain risk. When using third-party images: + +1. **Verify image sources** - Only use images from trusted publishers. The `catthehacker` images are community-maintained and not officially supported by GitHub. + +2. **Review image contents** - Understand what tools and configurations are included. Third-party images may contain pre-installed software that could behave unexpectedly. + +3. **Pin specific versions** - Use image digests (e.g., `@sha256:...`) instead of mutable tags to prevent tag manipulation: + ```bash + --agent-base-image ghcr.io/catthehacker/ubuntu@sha256:abc123... + ``` + +4. **Monitor for vulnerabilities** - Third-party images may not receive timely security updates compared to official images. + +**Existing security controls remain in effect:** +- Host-level iptables (DOCKER-USER chain) enforce egress filtering regardless of container contents +- Squid proxy enforces domain allowlist at L7 +- NET_ADMIN capability is dropped before user command execution +- Seccomp profile blocks dangerous syscalls +- `no-new-privileges` prevents privilege escalation + +**For maximum security, use the default `ubuntu:22.04` image.** Custom base images are recommended only when you trust the image publisher and the benefits outweigh the supply chain risks. + +### Pre-installed Tools + +The default `ubuntu:22.04` image includes: +- Node.js 22 +- Docker CLI +- curl, git, iptables +- CA certificates +- Network utilities (dnsutils, net-tools, netcat) + +When using runner images, you get additional tools like: +- Multiple Python, Node.js, Go, Ruby versions +- Build tools (make, cmake, gcc) +- AWS CLI, Azure CLI, GitHub CLI +- Container tools (docker, buildx) +- And many more (see [catthehacker/docker_images](https://github.com/catthehacker/docker_images)) + +### Notes + +- Custom base images only work with `--build-local` (not GHCR images) +- First build with a new base image will take longer (downloading the image) +- Subsequent builds use Docker cache and are faster +- The `full-22.04` image requires significant disk space (~60GB extracted) + ## Limitations ### No Internationalized Domains diff --git a/src/cli.test.ts b/src/cli.test.ts index 5f2248c8..828ecee4 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -1,5 +1,5 @@ import { Command } from 'commander'; -import { parseEnvironmentVariables, parseDomains, parseDomainsFile, escapeShellArg, joinShellArgs, parseVolumeMounts, isValidIPv4, isValidIPv6, parseDnsServers } from './cli'; +import { parseEnvironmentVariables, parseDomains, parseDomainsFile, escapeShellArg, joinShellArgs, parseVolumeMounts, isValidIPv4, isValidIPv6, parseDnsServers, validateAgentBaseImage } from './cli'; import { redactSecrets } from './redact-secrets'; import * as fs from 'fs'; import * as path from 'path'; @@ -774,4 +774,149 @@ describe('cli', () => { expect(DEFAULT_DNS_SERVERS).toEqual(['8.8.8.8', '8.8.4.4']); }); }); + + describe('validateAgentBaseImage', () => { + describe('valid images', () => { + it('should accept official Ubuntu images', () => { + expect(validateAgentBaseImage('ubuntu:22.04')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ubuntu:24.04')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ubuntu:20.04')).toEqual({ valid: true }); + }); + + it('should accept catthehacker runner images', () => { + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:runner-22.04')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:runner-24.04')).toEqual({ valid: true }); + }); + + it('should accept catthehacker full images', () => { + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:full-22.04')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:full-24.04')).toEqual({ valid: true }); + }); + + it('should accept catthehacker act images', () => { + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:act-22.04')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:act-24.04')).toEqual({ valid: true }); + }); + + it('should accept images with SHA256 digest pinning', () => { + expect(validateAgentBaseImage('ubuntu:22.04@sha256:a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:runner-22.04@sha256:a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:full-22.04@sha256:a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:act-22.04@sha256:a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1')).toEqual({ valid: true }); + }); + }); + + describe('invalid images', () => { + it('should reject arbitrary images', () => { + const result = validateAgentBaseImage('malicious-registry.com/evil:latest'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject images with typos', () => { + const result = validateAgentBaseImage('ubunto:22.04'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject non-ubuntu official images', () => { + const result = validateAgentBaseImage('alpine:latest'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject unknown registries', () => { + const result = validateAgentBaseImage('docker.io/library/ubuntu:22.04'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject images from other catthehacker registries', () => { + const result = validateAgentBaseImage('ghcr.io/catthehacker/debian:latest'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject ubuntu with non-standard tags', () => { + const result = validateAgentBaseImage('ubuntu:latest'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject empty image string', () => { + const result = validateAgentBaseImage(''); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject ubuntu with only major version', () => { + const result = validateAgentBaseImage('ubuntu:22'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject catthehacker with wrong prefix', () => { + const result = validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:minimal-22.04'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject malformed SHA256 digest (too short)', () => { + const result = validateAgentBaseImage('ubuntu:22.04@sha256:abc123'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should reject image with path traversal attempt', () => { + const result = validateAgentBaseImage('../ubuntu:22.04'); + expect(result.valid).toBe(false); + expect(result.error).toContain('Invalid base image'); + }); + + it('should provide helpful error message with allowed options', () => { + const result = validateAgentBaseImage('invalid:image'); + expect(result.valid).toBe(false); + expect(result.error).toContain('ubuntu:XX.XX'); + expect(result.error).toContain('ghcr.io/catthehacker/ubuntu:runner-XX.XX'); + expect(result.error).toContain('ghcr.io/catthehacker/ubuntu:full-XX.XX'); + expect(result.error).toContain('ghcr.io/catthehacker/ubuntu:act-XX.XX'); + expect(result.error).toContain('@sha256:'); + }); + }); + + describe('regex pattern coverage', () => { + // Ensure each regex pattern in SAFE_BASE_IMAGE_PATTERNS is individually tested + it('should match pattern 1: plain ubuntu version', () => { + expect(validateAgentBaseImage('ubuntu:18.04')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ubuntu:26.10')).toEqual({ valid: true }); + }); + + it('should match pattern 2: catthehacker runner/full/act without digest', () => { + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:runner-18.04')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:full-26.10')).toEqual({ valid: true }); + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:act-22.04')).toEqual({ valid: true }); + }); + + it('should match pattern 3: catthehacker with SHA256 digest', () => { + const digest = 'sha256:' + '1234567890abcdef'.repeat(4); + expect(validateAgentBaseImage(`ghcr.io/catthehacker/ubuntu:runner-22.04@${digest}`)).toEqual({ valid: true }); + expect(validateAgentBaseImage(`ghcr.io/catthehacker/ubuntu:full-24.04@${digest}`)).toEqual({ valid: true }); + expect(validateAgentBaseImage(`ghcr.io/catthehacker/ubuntu:act-22.04@${digest}`)).toEqual({ valid: true }); + }); + + it('should match pattern 4: plain ubuntu with SHA256 digest', () => { + const digest = 'sha256:' + 'abcdef1234567890'.repeat(4); + expect(validateAgentBaseImage(`ubuntu:22.04@${digest}`)).toEqual({ valid: true }); + expect(validateAgentBaseImage(`ubuntu:24.04@${digest}`)).toEqual({ valid: true }); + }); + + it('should reject images that almost match but do not exactly', () => { + // Nearly matching but invalid + expect(validateAgentBaseImage('ubuntu:22.04 ').valid).toBe(false); // trailing space + expect(validateAgentBaseImage(' ubuntu:22.04').valid).toBe(false); // leading space + expect(validateAgentBaseImage('Ubuntu:22.04').valid).toBe(false); // capital U + expect(validateAgentBaseImage('ghcr.io/catthehacker/ubuntu:Runner-22.04').valid).toBe(false); // capital R + }); + }); + }); }); diff --git a/src/cli.ts b/src/cli.ts index eaf5e9a5..caf480d7 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -101,6 +101,49 @@ export function isValidIPv6(ip: string): boolean { return isIPv6(ip); } +/** + * Safe patterns for agent base images to prevent supply chain attacks. + * Allows: + * - Official Ubuntu images (ubuntu:XX.XX) + * - catthehacker runner images (ghcr.io/catthehacker/ubuntu:runner-XX.XX, full-XX.XX, or act-XX.XX) + * - Images with SHA256 digest pinning + */ +const SAFE_BASE_IMAGE_PATTERNS = [ + // Official Ubuntu images (e.g., ubuntu:22.04, ubuntu:24.04) + /^ubuntu:\d+\.\d+$/, + // catthehacker runner images (e.g., ghcr.io/catthehacker/ubuntu:runner-22.04, act-24.04) + /^ghcr\.io\/catthehacker\/ubuntu:(runner|full|act)-\d+\.\d+$/, + // catthehacker images with SHA256 digest pinning + /^ghcr\.io\/catthehacker\/ubuntu:(runner|full|act)-\d+\.\d+@sha256:[a-f0-9]{64}$/, + // Official Ubuntu images with SHA256 digest pinning + /^ubuntu:\d+\.\d+@sha256:[a-f0-9]{64}$/, +]; + +/** + * Validates that a base image is from an approved source to prevent supply chain attacks. + * @param image - Docker image reference to validate + * @returns Object with valid boolean and optional error message + */ +export function validateAgentBaseImage(image: string): { valid: boolean; error?: string } { + // Check against safe patterns + const isValid = SAFE_BASE_IMAGE_PATTERNS.some(pattern => pattern.test(image)); + + if (isValid) { + return { valid: true }; + } + + return { + valid: false, + error: `Invalid base image: "${image}". ` + + 'For security, only approved base images are allowed:\n' + + ' - ubuntu:XX.XX (e.g., ubuntu:22.04)\n' + + ' - ghcr.io/catthehacker/ubuntu:runner-XX.XX\n' + + ' - ghcr.io/catthehacker/ubuntu:full-XX.XX\n' + + ' - ghcr.io/catthehacker/ubuntu:act-XX.XX\n' + + 'Use @sha256:... suffix for digest-pinned versions.' + }; +} + /** * Parses and validates DNS servers from a comma-separated string * @param input - Comma-separated DNS server string (e.g., "8.8.8.8,1.1.1.1") @@ -344,6 +387,14 @@ program 'Build containers locally instead of using GHCR images', false ) + .option( + '--agent-base-image ', + 'Base image for agent container when using --build-local. Options:\n' + + ' ubuntu:22.04 (default): Minimal, ~200MB\n' + + ' ghcr.io/catthehacker/ubuntu:runner-22.04: Closer to GitHub Actions, ~2-5GB\n' + + ' ghcr.io/catthehacker/ubuntu:full-22.04: Near-identical to GitHub Actions, ~20GB', + 'ubuntu:22.04' + ) .option( '--image-registry ', 'Container image registry', @@ -617,6 +668,7 @@ program tty: options.tty || false, workDir: options.workDir, buildLocal: options.buildLocal, + agentBaseImage: options.agentBaseImage, imageRegistry: options.imageRegistry, imageTag: options.imageTag, additionalEnv: Object.keys(additionalEnv).length > 0 ? additionalEnv : undefined, @@ -631,6 +683,22 @@ program allowedUrls, }; + // Validate and warn if using custom agent base image + if (options.agentBaseImage && options.agentBaseImage !== 'ubuntu:22.04') { + // Validate against approved base images for supply chain security + const validation = validateAgentBaseImage(options.agentBaseImage); + if (!validation.valid) { + logger.error(validation.error!); + process.exit(1); + } + + if (options.buildLocal) { + logger.info(`Using custom agent base image: ${options.agentBaseImage}`); + } else { + logger.warn('⚠️ --agent-base-image is only used with --build-local. Ignoring.'); + } + } + // Warn if --env-all is used if (config.envAll) { logger.warn('⚠️ Using --env-all: All host environment variables will be passed to container'); diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index 02951148..1029e036 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -205,6 +205,52 @@ describe('docker-manager', () => { expect(result.services.agent.image).toBeUndefined(); }); + it('should pass BASE_IMAGE build arg when agentBaseImage is specified', () => { + const customBaseImageConfig = { + ...mockConfig, + buildLocal: true, + agentBaseImage: 'ghcr.io/catthehacker/ubuntu:runner-22.04', + }; + const result = generateDockerCompose(customBaseImageConfig, mockNetworkConfig); + + expect(result.services.agent.build).toBeDefined(); + expect(result.services.agent.build?.args?.BASE_IMAGE).toBe('ghcr.io/catthehacker/ubuntu:runner-22.04'); + }); + + it('should not include BASE_IMAGE build arg when using default ubuntu:22.04', () => { + const localConfig = { ...mockConfig, buildLocal: true }; + const result = generateDockerCompose(localConfig, mockNetworkConfig); + + expect(result.services.agent.build).toBeDefined(); + // BASE_IMAGE should not be set when using the default (undefined or 'ubuntu:22.04') + expect(result.services.agent.build?.args?.BASE_IMAGE).toBeUndefined(); + }); + + it('should pass BASE_IMAGE build arg when agentBaseImage with SHA256 digest is specified', () => { + const customBaseImageConfig = { + ...mockConfig, + buildLocal: true, + agentBaseImage: 'ghcr.io/catthehacker/ubuntu:full-22.04@sha256:a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1', + }; + const result = generateDockerCompose(customBaseImageConfig, mockNetworkConfig); + + expect(result.services.agent.build).toBeDefined(); + expect(result.services.agent.build?.args?.BASE_IMAGE).toBe('ghcr.io/catthehacker/ubuntu:full-22.04@sha256:a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1'); + }); + + it('should not pass BASE_IMAGE when agentBaseImage is explicitly set to default ubuntu:22.04', () => { + const customBaseImageConfig = { + ...mockConfig, + buildLocal: true, + agentBaseImage: 'ubuntu:22.04', + }; + const result = generateDockerCompose(customBaseImageConfig, mockNetworkConfig); + + expect(result.services.agent.build).toBeDefined(); + // The code only sets BASE_IMAGE if agentBaseImage is defined (truthy), so ubuntu:22.04 would be set + expect(result.services.agent.build?.args?.BASE_IMAGE).toBe('ubuntu:22.04'); + }); + it('should use custom registry and tag', () => { const customConfig = { ...mockConfig, diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 29595d63..5bbb7b05 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -422,15 +422,22 @@ export function generateDockerCompose( if (useGHCR) { agentService.image = `${registry}/agent:${tag}`; } else { + const buildArgs: Record = { + // Pass host UID/GID to match file ownership in container + // This prevents permission issues with mounted volumes + USER_UID: getSafeHostUid(), + USER_GID: getSafeHostGid(), + }; + + // Allow custom base image for closer parity with GitHub Actions runner + if (config.agentBaseImage) { + buildArgs.BASE_IMAGE = config.agentBaseImage; + } + agentService.build = { context: path.join(projectRoot, 'containers/agent'), dockerfile: 'Dockerfile', - args: { - // Pass host UID/GID to match file ownership in container - // This prevents permission issues with mounted volumes - USER_UID: getSafeHostUid(), - USER_GID: getSafeHostGid(), - }, + args: buildArgs, }; } diff --git a/src/types.ts b/src/types.ts index 2301e566..966b195f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -147,6 +147,22 @@ export interface WrapperConfig { */ buildLocal?: boolean; + /** + * Base image for the agent container when building locally + * + * Allows customization of the agent container base image for closer parity + * with GitHub Actions runner environments. Only used when buildLocal is true. + * + * Options: + * - 'ubuntu:22.04' (default): Minimal image, smallest size (~200MB) + * - 'ghcr.io/catthehacker/ubuntu:runner-22.04': Closer to GitHub Actions runner (~2-5GB) + * - 'ghcr.io/catthehacker/ubuntu:full-22.04': Near-identical to GitHub Actions runner (~20GB compressed) + * + * @default 'ubuntu:22.04' + * @example 'ghcr.io/catthehacker/ubuntu:runner-22.04' + */ + agentBaseImage?: string; + /** * Additional environment variables to pass to the agent execution container * @@ -512,6 +528,8 @@ export interface DockerService { context: string; /** Path to the Dockerfile relative to context */ dockerfile: string; + /** Build arguments passed to docker build */ + args?: Record; }; /**