Overalls.

An AI-first developer platform that you own.

Guide Self-host
Design Architecture
Source GitHub · MIT Appearance

Deploy the control plane

Marshall and Lilly are NixOS modules exported by the flake. Enabling one doesn't enable the other. The host's configuration.nix chooses its roles, and secrets arrive as encrypted documents that are decrypted on the host.

Modules
nixosModules.default, or marshall, lilly, proxy, acme, secrets and backups individually
Target
x86_64-linux, NixOS 26.05

1. Build and check

nix build .#marshall .#lilly
nix build .#checks.x86_64-linux.marshall-release
nix build .#checks.x86_64-linux.lilly-release
nix build .#checks.x86_64-linux.module-configuration

Release builds have no network access, and dependency hashes are pinned. No production secrets are needed to build.

2. Enable Marshall

{
  imports = [ overalls.nixosModules.default ];
  services.marshall = {
    enable = true;
    publicHost = "control.example.com";
    replicaCount = 3;
    databaseConnectionBudget = 43;
    environmentFile = "/run/kittens-secrets/marshall.env";
    instances.node01 = {
      port = 4443;
      certificateFile = "/run/kittens-secrets/backend.crt";
      keyFile = "/run/kittens-secrets/backend.key";
    };
  };
}

Here overalls is your flake input for the fork. For a single server, set replicaCount = 1 and use local PostgreSQL; that's the supported nonredundant layout (ADR-0048 ↗). With automatic updates enabled, budget at least replicaCount × (poolSize + 3) + 10 database connections.

3. Secrets

services.kittensSecrets decrypts SOPS documents with the host's SSH-derived age key and publishes them under /run/kittens-secrets/NAME. Encrypt each document to the host and to your recovery users:

services.kittensSecrets = {
  enable = true;
  bundles.marshall = {
    encryptedFile = "${./secrets/marshall.enc.json}";
    outputs.environment = {
      format = "environment";
      secrets = [
        "DATABASE_URL" "MARSHALL_CLOAK_KEYS" "SECRET_KEY_BASE"
        "TOKEN_SIGNING_SECRET" "MARSHALL_SMTP_HOST" "MARSHALL_SMTP_USERNAME"
        "MARSHALL_SMTP_PASSWORD" "MARSHALL_SMTP_CA_CERTFILE" "MARSHALL_MAIL_FROM"
      ];
    };
  };
};
services.marshall = {
  environmentFile = "${config.services.kittensSecrets.bundles.marshall.directory}/environment";
  secretUnits = [ config.services.kittensSecrets.bundles.marshall.unit ];
};
nix build .#user-recipients --out-link result-recipient
nix run .#secrets -- check

4. Database and email

VariableMeaning
DATABASE_URLPostgreSQL, reached over verified TLS, shared by every Marshall
SECRET_KEY_BASE, TOKEN_SIGNING_SECRETShared signing secrets
MARSHALL_CLOAK_KEYSEncryption keyring. Keep old keys as long as data encrypted with them still exists.
MARSHALL_SMTP_HOST · _USERNAME · _PASSWORDYour relay. Plaintext SMTP isn't supported.
MARSHALL_SMTP_TLSimplicit (the default, port 465) or starttls (port 587)
MARSHALL_MAIL_FROMA verified sender, such as noreply@your-domain.example
PHX_HOSTThe public host name used in sign-in links

Marshall won't start if its SMTP configuration is missing or invalid. Send a test to a mailbox you control before you open the deployment to other people.

5. TLS and certificates

services.marshallProxy runs HAProxy in front of Marshall and verifies each backend's certificate and host name. The ACME integration issues and renews the public certificate:

services.marshallProxy.acme = {
  enable = true;
  email = "operator@example.com";
};
# Set only after accepting the selected CA's terms.
security.acme.acceptTerms = true;

HTTP-01 needs public DNS pointing at the frontend and port 80 reachable. Use challengeType = "dns-01" with Route 53 when port 80 can't be reachable. If several frontends share one host name, each lists the others as http01.peers.

6. Migrate, then switch

Serving instances never run migrations when they start. Instead, host enrollment and every automatic update run Marshall's migrations from the candidate release, under a PostgreSQL advisory lock, before the running services are stopped. Migrations must work with every release still serving, and a successful schema change is never rolled back automatically. Incompatible changes need a coordinated upgrade.

Full reference: docs/nixos.md

Full reference: docs/production-email.md