Network & Proxy Configuration
Enterprise environments often require that all outbound traffic from applications passes through a corporate proxy, firewall, or API gateway. Align provides two layers of network control for self-hosted deployments.
Layer 1: HTTP Proxy (Network-Level)
Route outbound traffic through a corporate HTTP/HTTPS proxy. The chart sets the proxy variables on the gateway, brain, UI and connector pods. It does not set them on the migrations, bootstrap, RLS cutover or telemetry Jobs, or on the bundled PostgreSQL, Redis, OTel collector and LLM server.
Configuration
global:
proxy:
# Forward proxy for HTTPS traffic (most common)
httpsProxy: "http://proxy.corp.example.com:3128"
# Forward proxy for HTTP traffic (optional, usually same as above)
httpProxy: "http://proxy.corp.example.com:3128"
# Additional hosts to bypass the proxy (comma-separated)
# Internal Align services are automatically excluded
noProxy: "*.internal.corp.com,10.0.0.0/8"
When configured, those pods receive HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables (both uppercase and lowercase for compatibility). Internal service-to-service traffic is automatically added to NO_PROXY.
How It Works
- Connectors (Slack, Teams, Jira, GitHub, Confluence, Zoom) use
undici.fetchwhich respectsHTTPS_PROXYautomatically - Teams Bot Framework SDK has explicit proxy agent support wired in - all token acquisition and proactive messaging traffic is routed through the proxy
- Brain service (Python/FastAPI) respects
HTTPS_PROXYviahttpxandaiohttp - Gateway respects proxy vars for any outbound calls (credential exchange, etc.)
Backward Compatibility
When no proxy values are configured, no environment variables are injected. Existing deployments are unaffected.
Layer 2: API Base URL Overrides (Application-Level)
For more granular control, you can redirect each connector's API traffic to a specific endpoint - for example, a corporate API gateway, reverse proxy, or custom MCP server that wraps the upstream API.
Per-Connector Configuration
connectors:
slack:
# Route all Slack API calls through your corporate gateway
apiBaseUrl: "https://api-gateway.corp.example.com/slack/api"
teams:
# Route all Microsoft Graph API calls through your corporate gateway
graphApiBaseUrl: "https://api-gateway.corp.example.com/graph"
jira:
# Route all Jira/Atlassian API calls through your corporate gateway
apiBaseUrl: "https://api-gateway.corp.example.com/atlassian/ex/jira"
github:
# GitHub Enterprise Server or corporate gateway
apiBaseUrl: "https://github.corp.example.com/api/v3"
linear:
# Route Linear API calls through your corporate gateway
apiBaseUrl: "https://api-gateway.corp.example.com/linear/graphql"
Environment Variables
Each override maps to an environment variable:
| Connector | Helm Value | Environment Variable | Default |
|---|---|---|---|
| Slack | connectors.slack.apiBaseUrl | SLACK_API_BASE_URL | https://slack.com/api |
| Teams | connectors.teams.graphApiBaseUrl | TEAMS_GRAPH_API_BASE_URL | https://graph.microsoft.com |
| Jira | connectors.jira.apiBaseUrl | JIRA_API_BASE_URL | https://api.atlassian.com/ex/jira |
| GitHub | connectors.github.apiBaseUrl | GITHUB_API_BASE_URL | https://api.github.com |
| Linear | connectors.linear.apiBaseUrl | LINEAR_API_BASE_URL | https://api.linear.app/graphql |
You can also set these via extraEnv on each connector if you prefer managing environment variables directly.
For Jira, the base URL must include the /ex/jira path prefix. The connector appends /{cloudId}/rest/api/3/... to this base. If routing through a gateway, ensure it proxies to api.atlassian.com/ex/jira with the correct path structure.
Kubernetes NetworkPolicy
For defense-in-depth, enable Kubernetes NetworkPolicies to apply a default-deny ingress posture and restrict egress at the network level.
What enabled: true ships
Ingress (default-deny + allow-list):
- Every Align pod denies ingress by default.
- Align pods may receive traffic from other Align pods in the same namespace (so gateway↔brain, ui→gateway, and connectors↔gateway keep working).
- Only the public-facing components (
gateway,ui,connector-align) accept external ingress, and only from the source you configure (below).
Egress:
- Pod-to-pod within the namespace and DNS (UDP/TCP 53, when
allowDNS: true). - External egress to
allowedExternalCIDRs(empty = allow all external). - The cloud metadata endpoint (
169.254.169.254, AWS IPv6 equivalent) is always blocked.
Configuration
networkPolicies:
enabled: true
ingress:
# Selector-routable controllers (e.g. ingress-nginx)
fromIngressController:
enabled: true
namespaceSelector:
kubernetes.io/metadata.name: ingress-nginx
podSelector: {}
# ALB/NLB with target-type=ip: traffic arrives from VPC IPs, not a pod.
# Allow your VPC / load-balancer subnet CIDR(s) instead.
allowedIngressCIDRs:
- 10.0.0.0/16
allowDNS: true
allowedExternalCIDRs:
- 10.0.0.0/8
- 172.16.0.0/12
With an AWS ALB/NLB using target-type: ip (the default in this chart),
requests reach pods directly from the load balancer's VPC IPs - a podSelector
will not match. Use allowedIngressCIDRs with your VPC CIDR. Use
fromIngressController only for in-cluster, selector-routable controllers like
ingress-nginx. If you configure neither, the public components accept no
external ingress (internal-only).
Rollout
NetworkPolicies are only enforced under a policy-capable CNI (Calico, Cilium, EKS VPC-CNI with policy enabled); on other clusters they are silently ignored. Because a too-tight allow-list can break ingress, enable on a non-prod environment first, confirm the app is reachable and gateway↔brain works, then promote to prod.
Protect the brain service only
The brain service has no authentication of its own. It trusts the x-tenant-id header, so
network isolation is its boundary. networkPolicies.brain.enabled: true ships one policy that
selects only the brain pod. It admits the gateway and the connectors that call brain,
from the same namespace, and nothing else. It does not touch any other pod and it does not
need networkPolicies.enabled.
networkPolicies:
brain:
enabled: true
# Default list: gateway, connector-jira, connector-confluence, connector-zoom,
# connector-linear. Add a peer here if you run another caller.
extraFrom: []
The default is false. Like every NetworkPolicy, it is ignored by a CNI that does not
enforce them. It composes with networkPolicies.enabled: when both are on, the any-Align-pod
allow rule skips the brain pod, so the brain list is not cancelled. A caller outside the chart
(a one-off job, for example) is not on the list and needs an entry in extraFrom. Test it on a non-production cluster first: from a pod outside the list, a call
to the brain service must time out.
Check that the cluster enforces it
The chart proves the policy objects (scripts/check-brain-networkpolicy-rendered.sh renders
the chart and checks that brain admits exactly the pods that call it). It cannot prove your
CNI enforces them. A policy object that exists is not a policy that works. Check on the live
cluster, in the target namespace, after enabling the policy:
# Must succeed (200): the gateway is on the list.
kubectl -n <ns> exec deploy/<release>-gateway -- \
node -e 'fetch("http://<release>-brain:8090/health").then(r=>console.log(r.status))'
# Must time out or be refused: the ui is not on the list.
kubectl -n <ns> exec deploy/<release>-ui -- \
node -e 'fetch("http://<release>-brain:8090/health",{signal:AbortSignal.timeout(5000)}).then(r=>console.log("REACHED",r.status),e=>console.log("blocked:",e.name))'
If the second call prints REACHED 200, the CNI is not enforcing NetworkPolicies. On EKS
the VPC CNI add-on needs network policy support turned on.
Pod-to-pod encryption (mTLS)
NetworkPolicies restrict who can talk to whom, not whether traffic is
encrypted. In-cluster gateway↔brain traffic is plain HTTP. For encryption in
transit between pods, run Align inside a service mesh that provides automatic
mTLS - Linkerd (simplest) or Istio in mTLS STRICT mode. Both wrap the
existing services with no application changes; combine the mesh's mTLS with these
NetworkPolicies for full transit hardening.
Required Outbound Domains
If you need to configure a firewall allowlist, here are the external domains Align connects to:
| Domain | Used By | Purpose |
|---|---|---|
slack.com | Slack connector | Slack API, OAuth |
graph.microsoft.com | Teams connector | Microsoft Graph API |
login.microsoftonline.com | Teams connector | Azure AD authentication |
login.botframework.com | Teams connector | Bot Framework token service |
smba.trafficmanager.net | Teams connector | Teams bot messaging |
api.atlassian.com | Jira connector | Jira Cloud API |
api.github.com | GitHub connector | GitHub API |
api.linear.app | Linear connector | Linear GraphQL API |
api.openai.com | Brain service | OpenAI LLM (if configured), and embeddings on the default profile |
api.anthropic.com | Brain service | Anthropic LLM (if configured) |
The host in license.checkin.url (not published until the licence service is live) | Gateway | The daily licence check-in: licence id, install id, version and people covered, signed. On by default for a license with an install key. Off with license.checkin.enabled: false, or with an offline license. Goes through global.proxy. See Licensing |
docker.io | Bundled Postgres and Redis, telemetry jobs | Only if you use them. The list is not complete: see OAuth apps for the connector hosts |
Hosts that are off by default, and what turns each on
A default install calls ONE host that Align runs: the licence check-in host in the table above (ALI-1950). The hosts below are not contacted by a default install (ALI-1926 checked PostHog, Resend and Grafana Cloud specifically; the connector and LLM hosts above are the ones you configure). Each needs a deliberate step from you:
| Host | What turns it on |
|---|---|
eu.i.posthog.com (PostHog) | ui.analytics.enabled: true, which only Align's hosted environments set. Do not create the align-posthog secret. |
api.resend.com (Resend) | A RESEND_API_KEY in the align-resend secret, which is your own Resend account. Sign-in codes and links, and the notification, lead and summary emails. |
| Grafana Cloud OTLP endpoint | observability.grafanaCloud.enabled: true with your own endpoint. Off by default. |
The https://align.tech/... addresses in the gateway source (pricing, demo and terms links in
emails, reports and error messages, and the licence issuer string) are text a person can click.
The server does not request them.
Combining Both Layers
For maximum security, use both layers together:
global:
proxy:
httpsProxy: "http://squid.corp.example.com:3128"
connectors:
slack:
apiBaseUrl: "https://api-gw.corp.example.com/slack/api"
teams:
graphApiBaseUrl: "https://api-gw.corp.example.com/graph"
jira:
apiBaseUrl: "https://api-gw.corp.example.com/atlassian/ex/jira"
github:
apiBaseUrl: "https://api-gw.corp.example.com/github"
networkPolicies:
enabled: true
allowDNS: true
allowedExternalCIDRs:
- 10.0.0.0/8
This ensures:
- All outbound HTTP traffic routes through your proxy (Layer 1)
- Each connector's API calls go through your API gateway (Layer 2)
- Kubernetes network policy blocks any unexpected egress (defense-in-depth)