Skip to content

Why I Rewrote a SaaS Stack as a Go Plugin Framework

The technical story behind Octarq: replacing fragmented microservices and vendor lock-in with a single, open-core Go binary and modular plugin system.

Updated View as Markdown

Most SaaS applications start out simple: a database, a backend server, and a frontend dashboard. As features scale — custom domains, mailbox routing, Stripe billing, SSO, role-based access control, notification dispatching, and audit logging — the architecture often becomes difficult to manage across separate microservices or locked into hosted infrastructure.

Octarq offers an open-core Go plugin framework that packs backoffice infrastructure (short links, mailboxes, DNS management, AI helpers, notifications) into a single binary, while allowing enterprise features (OIDC SSO, Audit Logs, Metered Billing) to be injected via decoupled plugins.

Here is the architectural story of why and how we built it.


1. The Core Philosophy: “Own the Stack”

When developers self-host software today, they shouldn’t need a Kubernetes cluster just to run a backoffice.

By building on Go and pure-Go SQLite (with Postgres driver support), Octarq compiles into a single static binary.

  • Zero-config startup: Running docker run -p 8080:8080 -v octarq-data:/data ghcr.io/octarq-org/octarq:latest auto-generates secret keys and admin credentials on first boot.
  • Data Sovereignty: All SQLite/Postgres data remains under the operator’s physical control.

2. Decoupled Seams & plugin.Context

Rather than hardcoding backoffice features into a monolithic app struct, Octarq core provides a strict, decoupled seam system. Every module — whether built-in or loaded as a custom plugin — mounts via plugin.Context:

type Plugin interface {
    Name() string
    Models() []any
    Mount(mux Mux, ctx *Context)
}

The *plugin.Context provides safe, isolated primitives:

  • Huma: Strongly-typed OpenAPI v3 route registration.
  • DB: Tenant-scoped database handles (orgDB(r)).
  • RequireRole: Fine-grained role-based authorization gates (rolegate).
  • Encrypt / Decrypt: Cryptographic storage at rest for API keys and capabilities.
  • Audit: Structured audit event recording with IP & actor tracking.

Plugin Registration Example

Because core features (like links, mail, and dns) use the exact same seam interfaces as custom extensions (telegram, webhook, audit), there are no “hidden internal APIs”. A plugin can register custom notification handlers, custom role permissions, or custom UI menus:

// Registering a Slack notification provider from a plugin
pctx.RegisterNotifier("slack", func(ctx context.Context, cfgJSON, text string) error {
    return slack.Send(ctx, cfgJSON, text)
})

3. OpenAPI-First with Huma v2

Rest APIs often suffer from documentation drift. Octarq resolves this by building every HTTP route using Huma v2 over standard http.ServeMux.

Every handler defines typed input and output structs with automatic JSON schema validation and OpenAPI 3.0 generation:

type CreateLinkInput struct {
    Body struct {
        Slug string `json:"slug" doc:"Shortlink slug"`
        URL  string `json:"url" format:"uri" doc:"Destination URL"`
    }
}

This guarantees that:

  1. Invalid requests are rejected automatically with structured 400 Bad Request errors.
  2. The entire API surface — core and plugins — is documented and browsable via /openapi.json and interactive API docs.
  3. Client SDKs (like @octarq-org/api-client) stay in sync with the backend OpenAPI definitions.

4. Defense-in-Depth & SSRF Protection

Security in a multi-tenant backoffice requires active defenses, not just password checks. Octarq implements multi-layered isolation:

  • Socket-Level SSRF Guards: Outbound webhook and SMTP delivery pass through safehttp.Control, which inspects final resolved IP addresses at dial time (net.Dialer.Control). Attempts to reach loopback (127.0.0.1), RFC1918 private subnets, or cloud metadata (169.254.169.254) are blocked before the socket connects, shutting down DNS rebinding attacks.
  • Role Ceiling Authorization: API tokens operate under an effectiveRole = min(live_membership, token_cap) security ceiling. Even if a token holds an owner role, revoking the user’s workspace membership immediately revokes the token.
  • Host Ownership Verification: Short links and mail domains enforce strict owner validation on creation, update, and resolution — preventing cross-tenant host squatting.

5. Architectural Decoupling & Pure Open Source

Octarq maintains strict codebase purity:

  • The core repository contains zero telemetry trackers, hidden upsells, or proprietary locks.
  • All capabilities adhere to decoupled Go modules and React UI packages.
  • Custom extensions compile directly against public plugin interfaces — without modifying core source code.

Summary

Navigation

Type to search…

↑↓ navigate↵ selectEsc close