A lightweight, high-performance A/B testing reverse proxy built in Go. Routes traffic between multiple backend services based on user segmentation. Specifically designed to aid marketing teams in A/B testing websites.
- Minimal Footprint: Compiled to a static binary (~9MB)
- Multi-Architecture: Supports ARM64, AMD64, and ARM via ko build
- Production Ready: Proper error handling and logging
- Flexible Base Images: Alpine (Dockerfile) or Chainguard static (ko) for minimal attack surface
Abbie acts as a reverse proxy that routes incoming HTTP requests to different backend services based on A/B test groups. Designed for any environment where you need dynamic traffic routing based on user attributes.
┌─────────┐
│ Client │
└────┬────┘
│
▼
┌────────────┐
│ Abbie │ (Route based on group)
└─────┬──────┘
│
├─────────► Landing Page A (Group A)
│
└─────────► Landing Page B (Group B)
Download the latest release for your platform from GitHub Releases:
# Example for Linux AMD64
wget https://github.com/DavidHoenisch/abbie/releases/latest/download/abbie_Linux_x86_64.tar.gz
tar -xzf abbie_Linux_x86_64.tar.gz
chmod +x abbie
# Run it
./abbie -config config.yamlBuild locally with the included Dockerfile:
# Build the image
docker build -t abbie:latest .
# Run with your config
docker run -d -p 8080:8080 \
-v $(pwd)/config.yaml:/etc/abbie/config.yaml \
abbie:latestgit clone https://github.com/DavidHoenisch/abbie.git
cd abbie
go build ./cmd/api
./api -config config.yaml# Run with a config file (required)
go run cmd/api/main.go -config config.yaml
# Or specify custom config and port
go run cmd/api/main.go -config config.local.yaml -port 9090
# Or use environment variables
ABBIE_CONFIG=config.yaml ABBIE_PORT=8080 go run cmd/api/main.go
# Show help
go run cmd/api/main.go -h# Build the image
docker build -t abbie:latest .
# Run with default config location
docker run -d -p 8080:8080 \
-v $(pwd)/config.yaml:/etc/abbie/config.yaml \
abbie:latest
# Run with custom config path and port
docker run -d -p 9000:9000 \
-v $(pwd)/config.yaml:/app/config.yaml \
abbie:latest -config /app/config.yaml -port 9000
# Run for local development
docker run -d -p 8080:8080 \
-v $(pwd)/config.local.yaml:/etc/abbie/config.yaml \
abbie:latest# Run production setup
docker-compose up -d
# Run development setup
docker-compose --profile dev up -d
# View logs
docker-compose logs -f
# Stop
docker-compose down# Build locally with ko
ko build --local ./cmd/api
# Note: When using ko, you'll need to mount config at runtime
docker run -p 8080:8080 \
-v $(pwd)/config.yaml:/etc/abbie/config.yaml \
ko.local/github.com/davidhoenisch/abbie/cmd/api:latest \
-config /etc/abbie/config.yamlAbbie requires a YAML configuration file for all routing and backend settings. The config can be provided in multiple ways:
- CLI flag:
-config /path/to/config.yaml(recommended) - Environment variable:
ABBIE_CONFIG=/path/to/config.yaml - Default: Looks for
config.yamlin the current directory
Create a config.yaml file in your project root (see config.example.yaml for examples):
app:
port: "8080"
backends:
- name: defense-backend
host: backend-a.example.com
port: 3000
groups:
- defense
- government
- name: healthcare-backend
host: backend-b.example.com
port: 3000
groups:
- healthcare
- medical
routing:
strategy: query-param # round-robin, query-param, header, cookie, static
param_name: audience # query param/header/cookie name to check
default_group: defense # fallback group when no match| Flag | Description | Default |
|---|---|---|
-config |
Path to config file | config.yaml |
-port |
Port to listen on (overrides config file) | Uses config file value |
-h |
Show help | - |
| Variable | Description | Default |
|---|---|---|
ABBIE_CONFIG |
Path to config file (overridden by -config flag) |
config.yaml |
ABBIE_PORT |
Port to listen on (overridden by -port flag) |
8080 |
Round-Robin: Distributes requests evenly across all backends
routing:
strategy: round-robinQuery Parameter: Routes based on query parameter (e.g., ?audience=defense)
routing:
strategy: query-param
param_name: audience
default_group: defenseHeader: Routes based on request header (e.g., X-Customer-Type: enterprise)
routing:
strategy: header
param_name: X-Customer-Type
default_group: standardCookie: Routes based on cookie value (e.g., A/B testing)
routing:
strategy: cookie
param_name: ab_test_group
default_group: AStatic: Always routes to the first configured backend
routing:
strategy: staticBackends can belong to multiple groups:
backends:
- name: my-backend
host: example.com
port: 3000
groups:
- defense
- government
- premiumWhen a request comes in with ?audience=defense, it will be routed to any backend that has defense in its groups.
abbie/
├── cmd/
│ └── api/
│ └── main.go # Application entry point
├── internal/
│ ├── config/
│ │ └── settings.go # Configuration management
│ └── router/
│ └── router.go # Request routing logic
├── .github/
│ └── workflows/ # CI/CD pipelines
├── .ko.yaml # ko build configuration
├── Dockerfile # Docker build configuration
├── docker-compose.yml # Docker Compose setup
├── .goreleaser.yml # GoReleaser configuration
└── README.md
Using the Dockerfile (recommended):
# Build and tag
docker build -t abbie:v1.0 .
# Run with mounted config (default location)
docker run -d -p 8080:8080 \
-v $(pwd)/config.yaml:/etc/abbie/config.yaml \
--name abbie \
abbie:v1.0
# Run with custom config path and port override
docker run -d -p 9000:9000 \
-v $(pwd)/config.yaml:/app/config.yaml \
--name abbie \
abbie:v1.0 -config /app/config.yaml -port 9000
# View logs
docker logs -f abbieUsing environment variables:
docker run -d -p 8080:8080 \
-v $(pwd)/config.yaml:/app/config.yaml \
-e ABBIE_CONFIG=/app/config.yaml \
-e ABBIE_PORT=8080 \
abbie:v1.0# Deploy using Dockerfile
fly deploy
# Or deploy using ko
fly deploy --image $(ko build ./cmd/api)Create a fly.toml with your config (using files section to mount config):
app = "your-app-name"
[build]
dockerfile = "Dockerfile"
[env]
ABBIE_CONFIG = "/etc/abbie/config.yaml"
[[services]]
internal_port = 8080
protocol = "tcp"
[[services.ports]]
handlers = ["http"]
port = 80With ConfigMap for config file:
apiVersion: v1
kind: ConfigMap
metadata:
name: abbie-config
data:
config.yaml: |
app:
port: "8080"
backends:
- name: backend-1
host: service-1.default.svc.cluster.local
port: 8080
groups:
- default
routing:
strategy: round-robin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: abbie
spec:
replicas: 3
selector:
matchLabels:
app: abbie
template:
metadata:
labels:
app: abbie
spec:
containers:
- name: abbie
image: ko://github.com/DavidHoenisch/abbie/cmd/api
ports:
- containerPort: 8080
env:
- name: ABBIE_CONFIG
value: "/etc/abbie/config.yaml"
volumeMounts:
- name: config
mountPath: /etc/abbie
volumes:
- name: config
configMap:
name: abbie-configThis project supports two build methods:
- Uses multi-stage build with Alpine base
- Good for familiar Docker workflows
- ~15MB final image size
- Standard Docker tooling
Uses ko for optimized container builds:
- Minimal base images (Chainguard static)
- Automatic multi-architecture builds (ARM64, AMD64, ARM)
- Fast, reproducible builds
- Built-in SBOM generation
- ~10MB final image size
- Binary Size: ~9MB static binary (uncompressed)
- Docker Image Size:
- Alpine (Dockerfile): ~15MB
- Chainguard static (ko): ~10MB
- Memory: <10MB RSS under normal load
- Latency: <1ms routing overhead
- Minimal Base Images:
- Alpine: Small, security-focused Linux distribution
- Chainguard static (ko): Distroless with no shell or package manager
- Static Binary: No runtime dependencies, compiled with Go 1.24
- SBOM: Automatic software bill of materials generation (via ko)
- Multi-layer Security: HTTPS support for proxied backends
Abbie uses GoReleaser with GitHub Actions for automated releases.
To create a new release:
# Tag your commit with semantic versioning
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0This will automatically:
- Build binaries for Linux, macOS, and Windows (AMD64, ARM64, ARM)
- Generate changelog from commit messages
- Create a GitHub Release with all artifacts
Available artifacts:
- Pre-compiled binaries for Linux, macOS, Windows (AMD64, ARM64, ARM)
- Checksums (SHA256)
- Automatically generated changelog
The project includes two GitHub Actions workflows:
CI Workflow (.github/workflows/ci.yml)
- Runs on every push to master/main and all PRs
- Executes tests with race detection and coverage
- Runs go vet and gofmt checks
- Runs golangci-lint for comprehensive linting
- Ensures code quality and build success
Release Workflow (.github/workflows/release.yml)
- Triggers on tag push (v*)
- Uses GoReleaser to build multi-platform binaries
- Creates GitHub Release with binaries and changelog
- Automatically generates checksums
MIT
Contributions welcome! Please open an issue or PR.