Skip to content

A configuragle, lightweight application router for A/B testing landing pages

License

Notifications You must be signed in to change notification settings

DavidHoenisch/abbie

Repository files navigation

Abbie

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.

Features

  • 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

How It Works

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)

Installation

Download Pre-built Binary

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.yaml

Docker

Build 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:latest

Build from Source

git clone https://github.com/DavidHoenisch/abbie.git
cd abbie
go build ./cmd/api
./api -config config.yaml

Quick Start

Local Development

# 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 with Docker

# 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

Docker Compose (Easiest)

# 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 with ko (Alternative)

# 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.yaml

Configuration

Abbie requires a YAML configuration file for all routing and backend settings. The config can be provided in multiple ways:

  1. CLI flag: -config /path/to/config.yaml (recommended)
  2. Environment variable: ABBIE_CONFIG=/path/to/config.yaml
  3. Default: Looks for config.yaml in the current directory

Configuration File

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

CLI Flags

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 -

Environment Variables

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

Routing Strategies

Round-Robin: Distributes requests evenly across all backends

routing:
  strategy: round-robin

Query Parameter: Routes based on query parameter (e.g., ?audience=defense)

routing:
  strategy: query-param
  param_name: audience
  default_group: defense

Header: Routes based on request header (e.g., X-Customer-Type: enterprise)

routing:
  strategy: header
  param_name: X-Customer-Type
  default_group: standard

Cookie: Routes based on cookie value (e.g., A/B testing)

routing:
  strategy: cookie
  param_name: ab_test_group
  default_group: A

Static: Always routes to the first configured backend

routing:
  strategy: static

Backend Groups

Backends can belong to multiple groups:

backends:
  - name: my-backend
    host: example.com
    port: 3000
    groups:
      - defense
      - government
      - premium

When a request comes in with ?audience=defense, it will be routed to any backend that has defense in its groups.

Project Structure

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

Deployment

Docker Deployment

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 abbie

Using 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

Fly.io

# 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 = 80

Kubernetes

With 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-config

Build Options

This project supports two build methods:

Docker (Traditional)

  • Uses multi-stage build with Alpine base
  • Good for familiar Docker workflows
  • ~15MB final image size
  • Standard Docker tooling

ko (Advanced)

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

Performance

  • 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

Security

  • 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

Releases

Creating a New Release

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.0

This 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

CI/CD

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

License

MIT

Contributing

Contributions welcome! Please open an issue or PR.

About

A configuragle, lightweight application router for A/B testing landing pages

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published