WireGuard Gateway

When flowgen needs to reach resources on a private network (e.g. an on-prem MSSQL server behind a VPN), the Helm chart can deploy a standalone WireGuard gateway pod. Flowgen pods connect to the gateway’s ClusterIP Service instead of reaching through the tunnel directly.

Why a separate pod?

WireGuard identifies each peer by its private key and IP address. If multiple flowgen replicas each run a WireGuard sidecar with the same identity, the VPN server sees conflicting handshakes and flaps connections — causing intermittent timeouts on every query.

A standalone gateway pod holds a single VPN identity. Flowgen pods connect to the forwarded ports via a normal Kubernetes Service, so the number of replicas doesn’t matter.

Architecture

flowgen pod 0 ──┐
flowgen pod 1 ──┼── ClusterIP Service ──► gateway pod ──► WireGuard tunnel ──► on-prem DB
flowgen pod 2 ──┘       :1433               (socat)            (wg0)           10.0.0.5:1433

The gateway pod runs three components:

  1. Init container — sets up iptables MASQUERADE and IP forwarding.
  2. WireGuard container — establishes the VPN tunnel from a config secret.
  3. socat sidecar — listens on local ports and forwards TCP to the remote host through the tunnel.

Configuration

1. Create the tunnel secret

Each tunnel needs a Kubernetes Secret containing the WireGuard config file. The secret key must match the tunnel name (e.g. wg0.conf for a tunnel named wg0).

apiVersion: v1
kind: Secret
metadata:
  name: wireguard-client-a
type: Opaque
stringData:
  wg0.conf: |
    [Interface]
    PrivateKey: <YOUR_PRIVATE_KEY>
    Address: 10.13.13.2/24
    DNS: 1.1.1.1
    MTU = 1420

    [Peer]
    PublicKey: <SERVER_PUBLIC_KEY>
    Endpoint: vpn.example.com:51820
    AllowedIPs: 10.0.0.0/8, 172.16.0.0/12
    PersistentKeepalive = 25

Setting MTU = 1420 prevents silent packet drops on large payloads (e.g. SQL result sets) caused by WireGuard encapsulation overhead.

2. Configure the gateway in values.yaml

wireguardGateway:
  enabled: true
  tunnels:
    - name: wg0
      secretName: wireguard-client-a
      portForwards:
        - name: mssql
          port: 1433
          targetHost: 10.0.0.5
          targetPort: 1433

Each entry in portForwards starts a socat process that listens on port and forwards to targetHost:targetPort through the tunnel.

3. Point flowgen at the gateway

In your flow config, use the gateway Service DNS name instead of the private IP:

tasks:
  - mssql_query:
      name: fetch_orders
      host: "<release-name>-flowgen-wireguard-gateway"
      port: 1433
      database: production
      query: { resource: orders.sql }

The Service name follows the pattern <release-name>-flowgen-wireguard-gateway. If your Helm release is called flowgen, the host is flowgen-wireguard-gateway.

Multiple port forwards

A single tunnel can forward multiple ports. Each socat process runs independently.

tunnels:
  - name: wg0
    secretName: wireguard-client-a
    portForwards:
      - name: mssql
        port: 1433
        targetHost: 10.0.0.5
        targetPort: 1433
      - name: postgres
        port: 5432
        targetHost: 10.0.0.6
        targetPort: 5432

If two remote services use the same port number, use servicePort to expose one on a different port in the Service:

portForwards:
  - name: mssql-a
    port: 1433
    targetHost: 10.0.0.5
    targetPort: 1433
  - name: mssql-b
    port: 1434
    targetHost: 10.0.0.6
    targetPort: 1433
    servicePort: 1434

Deployment details

SettingValueReason
Replicas1Only one pod can hold a given VPN identity.
StrategyRecreateAvoids two pods fighting over the same WireGuard key during rollouts.
Liveness probewg show wg0 \| grep 'latest handshake'Restarts the pod if the tunnel drops.
Readiness probeSame as livenessKeeps the Service endpoint out of rotation until the handshake completes.

Values reference

FieldTypeDefaultDescription
wireguardGateway.enabledboolfalseDeploy the gateway pod and Service.
wireguardGateway.image.repositorystringghcr.io/linuxserver/wireguardWireGuard container image.
wireguardGateway.image.tagstring1.0.20250521-r1-ls105Image tag.
wireguardGateway.tunnelsarray[]List of tunnel configurations.
wireguardGateway.tunnels[].namestringTunnel name. Maps to the config file name (<name>.conf).
wireguardGateway.tunnels[].secretNamestringKubernetes Secret containing the WireGuard config.
wireguardGateway.tunnels[].portForwardsarrayPorts to forward through this tunnel via socat.
wireguardGateway.tunnels[].portForwards[].namestringPort name (used in Service port naming).
wireguardGateway.tunnels[].portForwards[].portintLocal listen port for socat.
wireguardGateway.tunnels[].portForwards[].targetHoststringRemote host reachable through the tunnel.
wireguardGateway.tunnels[].portForwards[].targetPortintRemote port on the target host.
wireguardGateway.tunnels[].portForwards[].servicePortintsame as portPort exposed in the Kubernetes Service.
wireguardGateway.socat.imagestringalpine/socat:1.8.0.1socat sidecar image.
wireguardGateway.resourcesobjectResource requests/limits for the WireGuard container.
wireguardGateway.socat.resourcesobjectResource requests/limits for the socat sidecar.
wireguardGateway.nodeSelectorobject{}Node selector for the gateway pod.
wireguardGateway.tolerationsarray[]Tolerations for the gateway pod.
wireguardGateway.affinityobject{}Affinity rules for the gateway pod.