Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

lynicis/applecontainer-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

46 Commits

🍎 applecontainer-go

Go Reference Go Report Card codecov License: MIT

applecontainer-go is a lightweight, testcontainers-go-style Go library designed to spin up Apple Container (container CLI) Linux containers as test dependencies on macOS.

Unlike Docker-based libraries, applecontainer-go integrates directly with the native Apple Silicon container virtualization engine, letting you boot up test dependencies like databases, web servers, and queues with near-zero overhead.


Table of Contents


Features

  • 🏎️ Fast Boot times: Harness macOS native container technology for minimal latency.
  • Familiar API: Modelled closely after testcontainers-go to ensure a minimal learning curve.
  • 🌐 Double Networking Modes: Choose between Bridged Direct IP and Host-Port mapping models.
  • ⏱️ Robust Wait Strategies: Built-in support for HTTP, listening ports, logs, SQL databases, executions, and file existence.
  • 🏗️ Build from Context: Support for building images on the fly via Containerfile / Dockerfile.
  • 🪝 Lifecycle Hooks: Attach custom execution callbacks to pre/post container build, creation, start, and termination phases.

Prerequisites

  • Host OS: macOS 26+ (Apple native container orchestration environment)
  • Architecture: Apple Silicon (M1, M2, M3, M4, or newer)
  • Dependency: The Apple native container CLI must be installed and active (e.g. /opt/homebrew/bin/container or /usr/local/bin/container).

Install

go get -u github.com/lynicis/applecontainer-go

Quickstart

Spin up an Nginx container with an HTTP wait strategy and get the endpoint:

package main
import (
	"context"
	"fmt"
	"net/http"
	"time"
	"github.com/lynicis/applecontainer-go"
	"github.com/lynicis/applecontainer-go/wait"
)
func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()
	// Spin up Nginx in direct container IP mode
	c, err := applecontainer.Run(ctx, "nginx:alpine",
		applecontainer.WithExposedPorts("80"),
		applecontainer.WithWaitStrategy(wait.ForHTTP("/").WithPort("80")),
	)
	if err != nil {
		panic(err)
	}
	defer c.Terminate(ctx)
	// Fetch endpoint (resolves to direct container IP:80 by default)
	endpoint, err := c.Endpoint(ctx, "80")
	if err != nil {
		panic(err)
	}
	resp, err := http.Get("http://" + endpoint)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	fmt.Printf("Nginx status: %s\n", resp.Status)
}

Networking Models

applecontainer-go supports two distinct networking architectures:

1. Direct IP Mode (Default)

In this mode, container virtual interfaces map directly onto the macOS host bridge.

  • You can communicate directly with the container's private bridge IP.
  • c.Host(ctx) resolves to the container's IP address.
  • c.Endpoint(ctx, port) matches the container's IP and port directly.
  • Limitation: No host ports are consumed, making it ideal for running many concurrent integration tests.

2. Host-Port Mapping Mode (Opt-in)

To bind container ports to ephemeral host ports on the localhost interface, enable this option using WithHostPortMapping(true).

  • Ephemeral host ports are automatically allocated and bound to the host network interface.
  • c.Host(ctx) resolves to "localhost".
  • c.Endpoint(ctx, port) resolves to localhost:<random_host_port>.
c, err := applecontainer.Run(ctx, "nginx:alpine",
	applecontainer.WithExposedPorts("80"),
	applecontainer.WithHostPortMapping(true),
	applecontainer.WithWaitStrategy(wait.ForHTTP("/").WithPort("80")),
)

Wait Strategies

A container isn't always fully initialized as soon as it starts. Wait strategies allow you to block execution until the dependency is ready.

Strategy Constructor Description
Listening Port wait.ForListeningPort(port) Checks if a TCP/UDP port is open and listening.
HTTP wait.ForHTTP(path) Performs HTTP requests and validates status codes/response body matchers.
Log Stream wait.ForLog(pattern) Scans stdout/stderr logs for a substring or regexp pattern.
Exec Command wait.ForExec(cmd) Executes a command inside the container repeatedly until exit code or outputs match.
SQL Database wait.ForSQL(port, driver, dburl) Verifies database availability using standard Go database/sql driver and query.
File Check wait.ForFile(path) Verifies file existence inside the container filesystem.
Container Health wait.ForHealth() Waits for the container state to be "running" with exit code 0.
All (Composite) wait.ForAll(strats...) Runs all provided wait strategies sequentially.
Any (Composite) wait.ForAny(strats...) Runs all provided wait strategies concurrently, succeeding when any one does.

Wait Strategy Examples

// Wait for Postgres using SQL driver pgx
dburl := func(host string, port int) string {
	return fmt.Sprintf("postgres://postgres:postgres@%s:%d/postgres?sslmode=disable", host, port)
}
waitPostgres := wait.ForSQL("5432", "pgx", dburl).WithQuery("SELECT 1")
// Wait for a application server healthcheck endpoint
waitApp := wait.ForHTTP("/health").
	WithPort("8080").
	WithStatusCodeMatcher(func(code int) bool { return code == http.StatusOK })
// Wait for a log occurrence 3 times
waitLogs := wait.ForLog("server started").WithOccurrence(3)

Customizer Options

Customize the container configuration using functional options:

Customizer Option Type / Arguments Purpose
WithImage(img) string Defines the container image.
WithExposedPorts(ports...) ...string Ports exposed from the container (e.g. "80/tcp").
WithHostPortMapping(bool) bool Enables mapping exposed ports to host localhost ephemeral ports.
WithEnv(map) map[string]string Key-value environment variables.
WithCmd(cmd...) ...string Command parameters passed to the container.
WithEntrypoint(entry...) ...string Container entrypoint command.
WithNetworks(names...) ...string Custom network names to attach the container to.
WithVolumes(vols...) ...VolumeMount Directory volumes to mount.
WithTmpfs(map) map[string]string Mount tmpfs directories.
WithCPUs(cpus) float64 CPU allocation limits.
WithMemory(bytes) int64 Memory allocation limits.
WithWaitStrategy(strat) wait.Strategy Configures the default wait strategy with a default 60s timeout.
WithContainerfile(cf) FromContainerfile Build image from a context directory using Containerfile.
WithLifecycleHooks(hooks) ContainerLifecycleHooks Hooks to inject custom callbacks into lifecycle states.
WithLogConsumers(c...) ...LogConsumer Register consumers to receive stream container stdout/stderr.

Lifecycle Hooks

You can hook into different phases of the container's lifecycle:

hooks := applecontainer.ContainerLifecycleHooks{
	PreStarts: []applecontainer.ContainerHook{
		func(ctx context.Context, c applecontainer.Container) error {
			fmt.Println("Container is about to start!")
			return nil
		},
	},
	PostReadies: []applecontainer.ContainerHook{
		func(ctx context.Context, c applecontainer.Container) error {
			fmt.Println("Container is ready and wait strategies passed!")
			return nil
		},
	},
}
c, err := applecontainer.Run(ctx, "nginx:alpine",
	applecontainer.WithLifecycleHooks(hooks),
)

Networks and Volumes

Networks

Create and attach containers to custom networks to facilitate inter-container communication:

nw, err := applecontainer.NewNetwork(ctx,
	applecontainer.WithNetworkLabels(map[string]string{"type": "integration"}),
)
defer nw.Remove(ctx)
c1, err := applecontainer.Run(ctx, "nginx:alpine",
	applecontainer.WithNetwork([]string{"web-server"}, nw),
)

Volumes

Easily create volumes and mount them to containers to persist data across container lifecycles:

vol, err := applecontainer.NewVolume(ctx,
	applecontainer.WithVolumeSize("10G"),
)
defer vol.Remove(ctx)
c, err := applecontainer.Run(ctx, "postgres:alpine",
	applecontainer.WithVolumes(applecontainer.VolumeMount{
		Source: vol.Name(),
		Target: "/var/lib/postgresql/data",
	}),
)

Benchmarks

Performance comparison: applecontainer-go (Apple native container CLI) vs testcontainers-go (Docker Desktop).

Hardware: Apple M5 · macOS 26 · container CLI v1.0.0 · Docker Desktop v29.4.0 Method: benchtime=1x, single iteration per benchmark.

Cold Start (3 images)

Container applecontainer-go testcontainers-go Δ
nginx:alpine 1.90s
redis:alpine 2.15s
postgres:alpine 7.46s

Operation Latency

Operation applecontainer-go testcontainers-go Δ
Stop 253ms 294ms 1.2x faster
Start 84ms 260ms 3.1x faster
Terminate 254ms 336ms 1.3x faster
Inspect 304ms 313ms ≈ same
Exec (echo) 341ms 499ms 1.5x faster
Copy 1 KB 296ms 301ms ≈ same
Copy 1 MB 293ms 302ms ≈ same

Network Performance

Test applecontainer-go testcontainers-go Δ
TCP Latency (redis PING) 296ms 688ms 2.3x faster
HTTP Throughput (nginx) N/A 2.16s

Parallel Startup (nginx:alpine)

Containers applecontainer-go testcontainers-go Δ
2 1.52s 658ms 2.3x slower
4 3.39s 1.13s 3.0x slower
8 6.54s 2.05s 3.2x slower

Image Operations

Test applecontainer-go testcontainers-go Δ
Image Pull (postgres:alpine) 6.44s 2.05s 3.1x slower
Image Build N/A 16ms

Key takeaways:

  • Start/Stop/Terminate/Exec are significantly faster with the native Apple runtime — up to 3x for Start.
  • Parallel scalability is limited — the Apple CLI processes containers sequentially, while Docker runs them concurrently.
  • Image operations (pull, build) are slower due to the Apple CLI's image management overhead.
  • HTTP throughput from host is not supported in Apple's direct-IP networking model.

Run benchmarks locally:

cd benchmarks
APPLECONTAINER_BENCHMARK=1 go test -bench=. -benchtime=1x -tags benchmark -timeout=600s ./...

Testing and Verification

Unit Tests

Unit tests use mocking and system CLI fakes and do not require the actual Apple container runtime environment.

go test ./...

Integration Tests

Integration tests run real container scenarios and require macOS with a running container CLI daemon.

APPLECONTAINER_INTEGRATION=1 go test -tags integration -v ./examples/...

Contributing

We welcome issues, bug reports, and pull requests to improve applecontainer-go.

Please read our Contributing Guide for details on how to set up the local development environment, run tests, and follow code quality guidelines.

Briefly:

  1. Fork the repository.
  2. Create your feature branch (git checkout -b feature/amazing-feature).
  3. Commit your changes (git commit -m 'Add amazing feature').
  4. Push to the branch (git push origin feature/amazing-feature).
  5. Open a Pull Request.

License

Distributed under the MIT License. See LICENSE for more information.

About

A testcontainers-go-style Go library for spinning up Apple Container CLI Linux containers as test dependencies on macOS.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors

AltStyle によって変換されたページ (->オリジナル) /