Skip to content

Private Network Deployment

This guide covers deploying your Convox rack and Rack Gateway on a completely private network, with no public internet exposure.

By default, the Convox API is exposed publicly via nginx ingress with authentication via the rack password. While protected by authentication, this presents security concerns:

  • Discoverable endpoint - The API is subject to brute force attempts
  • Token compromise risk - Any rack token compromise grants API access from anywhere
  • No network-level access control - Cannot enforce IP restrictions for sensitive operations
  • Compliance requirements - Some organizations require all internal tools to be on private networks

With a fully private deployment:

  • The Convox API is only accessible through your VPN/private network
  • Even if the rack token is compromised, attackers can’t reach the API
  • You gain defense-in-depth: network security + authentication + RBAC

There’s an open PR (#935) to add a private_api parameter to Convox that disables public access to the API entirely.

DocSpring maintains a public fork of Convox that includes the private_api feature along with other improvements:

  • private_api - Disable public nginx ingress for the Convox API
  • CloudFlare DNS-01 resolver - Alternative certificate validation method
  • Various bug fixes and enhancements
  1. Set up Tailscale Operator in your cluster

    Follow the Tailscale Kubernetes operator guide to install the operator in your EKS cluster.

  2. Configure your Convox rack with private_api

    Use DocSpring’s fork in your Terraform configuration:

    module "system" {
    source = "github.com/DocSpring/convox//terraform/system/aws?ref=docspring"
    image = "docspringcom/convox"
    release = "docspring-004"
    name = "production"
    region = "us-east-1"
    # Private EKS API access (existing Convox feature)
    private_eks_host = "https://eks-production.your-tailnet.ts.net"
    disable_public_access = true
    # Private Convox API access (DocSpring fork feature)
    private_api = true
    # ... other configuration ...
    }
  3. Create Tailscale ingress for the Convox API

    resource "kubernetes_ingress_v1" "convox_api_tailscale" {
    metadata {
    name = "convox-api"
    namespace = "rack-system" # or your rack namespace
    annotations = {
    "tailscale.com/expose" = "true"
    "tailscale.com/hostname" = "convox-api-production"
    "tailscale.com/tags" = "tag:convox-api"
    }
    }
    spec {
    ingress_class_name = "tailscale"
    rule {
    host = "convox-api-production.your-tailnet.ts.net"
    http {
    path {
    path = "/"
    path_type = "Prefix"
    backend {
    service {
    name = "api"
    port { name = "https" }
    }
    }
    }
    }
    }
    tls {
    hosts = ["convox-api-production.your-tailnet.ts.net"]
    }
    }
    }
  4. Deploy Rack Gateway with Tailscale access

    Configure Rack Gateway to connect to the private Convox API:

    Terminal window
    # In your Rack Gateway environment
    RACK_HOST=https://convox-api-production.your-tailnet.ts.net
    RACK_TOKEN=your-rack-token

    And expose Rack Gateway itself via Tailscale:

    resource "kubernetes_ingress_v1" "rack_gateway_tailscale" {
    metadata {
    name = "rack-gateway"
    namespace = "rack-gateway"
    annotations = {
    "tailscale.com/expose" = "true"
    "tailscale.com/hostname" = "gateway-production"
    "tailscale.com/tags" = "tag:rack-gateway"
    }
    }
    spec {
    ingress_class_name = "tailscale"
    rule {
    host = "gateway-production.your-tailnet.ts.net"
    http {
    path {
    path = "/"
    path_type = "Prefix"
    backend {
    service {
    name = "rack-gateway"
    port { number = 8447 }
    }
    }
    }
    }
    }
    tls {
    hosts = ["gateway-production.your-tailnet.ts.net"]
    }
    }
    }
  5. Configure Tailscale ACLs

    In your Tailscale admin console, set up ACLs to control who can access what:

    {
    "acls": [
    {
    "action": "accept",
    "src": ["group:developers"],
    "dst": ["tag:rack-gateway:443"]
    },
    {
    "action": "accept",
    "src": ["tag:rack-gateway"],
    "dst": ["tag:convox-api:443"]
    }
    ],
    "tagOwners": {
    "tag:convox-api": ["group:infrastructure"],
    "tag:rack-gateway": ["group:infrastructure"]
    }
    }
Terminal window
# Should timeout or return DNS error
curl https://api.your-rack-domain.convox.cloud
# Connection refused or timeout expected
Terminal window
# Connect to Tailscale first
tailscale up
# Test Convox API via Tailscale
curl https://convox-api-production.your-tailnet.ts.net/system
# Test Rack Gateway via Tailscale
curl https://gateway-production.your-tailnet.ts.net/api/v1/health
Terminal window
# Login using the Tailscale hostname
rack-gateway login production https://gateway-production.your-tailnet.ts.net
# Verify commands work
rack-gateway convox apps

While we recommend Tailscale for its simplicity, you can achieve private network access with other methods:

Pros:

  • Zero-config mesh VPN
  • Works across cloud providers
  • Built-in ACLs and audit logging
  • Easy to set up Kubernetes operator

Cons:

  • Requires Tailscale subscription for teams
  • External dependency

With a fully private deployment, you achieve defense-in-depth:

LayerProtection
NetworkAPI only accessible via private network
AuthenticationGoogle Workspace OAuth via Rack Gateway
AuthorizationRBAC controls what each user can do
AuditEvery action logged with user attribution
MFAAdditional verification for sensitive operations

Even if an attacker obtains:

  • Rack token alone → Can’t reach the API (network blocked)
  • Tailscale access alone → Can’t authenticate (no OAuth session)
  • OAuth session alone → Limited by RBAC permissions
  • All of the above → Actions are logged and auditable