How do I route all providers through the Private Network Agent automatically?
Last updated: October 19, 2026
Goal
Set up one Proxy on a control plane so that every provider, including ones
installed later or pulled in by a Configuration, goes through the Private
Network Agent with no further changes.
Environment
- Upbound Cloud Spaces control planes with the Private Network Agent.
- Proxy in spaces.upbound.io/v1alpha1
Prerequisites
- Admin role on the control plane, which creating a Proxy requires
- Private Network Agent installed in your private network
- The up CLI, kubectl and jq
- Permission to create robots in your Upbound organization
- No providers installed on the control plane yet, if you are starting fresh
Steps
Create the Proxy with a podSelector on the pkg.crossplane.io/provider
label. The selector is evaluated every time a pod is created on the control
plane, and Crossplane puts that label on every provider pod, so providers that
don't exist yet are covered too. Matching pods get HTTP_PROXY, HTTPS_PROXY
and NO_PROXY injected.
All outbound traffic from a matched provider goes through the agent. That includes calls to cloud provider APIs. If the private network can't reach those endpoints, or the agent is down, matched providers can't reach their targets. Exclude any provider that shouldn't be routed (see Notes).
1. Create a robot and token for the proxy
The robot only needs to belong to your organization, not to any team. A
dedicated robot with no team memberships keeps the token limited to
establishing the proxy connection.
up robot create private-network-proxy --description "Private Network Agent proxy"
up robot token create private-network-proxy proxy-token -f proxy-token.json2. Store the token on the control plane
up ctx <org>/<space>/<group>/<control-plane>
kubectl -n default create secret generic robot-token \
--from-literal=token="$(jq -r .token proxy-token.json)"
rm proxy-token.json3. Create the Proxy before installing providers
agentID is optional. Leave it out and one is generated for you, shown in
status.agentID.
apiVersion: spaces.upbound.io/v1alpha1
kind: Proxy
metadata:
name: private-net
namespace: default
spec:
description: "Route all providers through the Private Network Agent"
agentID: "<your-agent-id>" # optional; omit to auto-generate (see status.agentID)
secretRef:
name: robot-token
key: token
podSelector:
matchExpressions:
- key: pkg.crossplane.io/provider
operator: Existskubectl apply -f proxy.yaml
kubectl get proxy private-net -n default # wait for PHASE=Connected4. Install providers or Configurations as usual
kubectl apply -f <configuration>.yamlVerify
Every provider pod should show the proxy URL. <none> means the pod was not
injected.
kubectl get pods -n crossplane-system -l pkg.crossplane.io/provider \
-o custom-columns='POD:.metadata.name,PROVIDER:.metadata.labels.pkg\.crossplane\.io/provider,HTTPS_PROXY:.spec.containers[0].env[?(@.name=="HTTPS_PROXY")].value'POD PROVIDER HTTPS_PROXY
upbound-provider-helm-f131ef928c22-7d5cff6777-stxwz upbound-provider-helm http://proxy-private-net.default.svc.cluster.local:8080
upbound-provider-kubernetes-aeab21ef60b7-7c558dd875-wmpkf upbound-provider-kubernetes http://proxy-private-net.default.svc.cluster.local:8080Excluding Providers
Label selectors don't support wildcards or prefixes, but you can exclude specific providers with NotIn. Values are Provider object names (metadata.name), for example upbound-provider-helm.
podSelector:
matchExpressions:
- key: pkg.crossplane.io/provider
operator: Exists
- key: pkg.crossplane.io/provider
operator: NotIn
values: [upbound-provider-helm]To route only a known set of providers instead, use operator: In with their names. You can list providers that aren't installed yet.
Notes
All outbound traffic of matched providers goes through the agent, including calls to cloud provider APIs. Make sure the private network can reach those endpoints, or exclude those providers. If the agent is unavailable, matched providers can't reach their targets.
One
Proxyper control plane. Choose a selector that covers everything the control plane needs.Injection requires
PHASE=Connected. Existing matching pods are restarted automatically when the Proxy connects or its selector changes.Custom pod labels are not supported through
DeploymentRuntimeConfigon Upbound Cloud, so select on thepkg.crossplane.io/providerlabel as shown above.Functions are not matched by this selector; they carry
pkg.crossplane.io/functioninstead.