Skip to main content
Guide

Private Endpoint

Configure private ingress for ports exposed by Sandboxes in a Sandbox Group.

Why use a Private Endpoint

Use a Private Endpoint when you must reach Sandbox workloads from a VNet, peered network, VPN, or ExpressRoute connection without exposing Sandbox ingress to the public internet. The Private Endpoint assigns a private IP from your VNet and carries traffic to a Container Apps Environment configured with mode=Express over Azure Private Link.

One Private Endpoint can provide this private ingress path for every Sandbox Group linked to the Container Apps Environment. You keep using the Sandbox port FQDN returned by the service; Private DNS resolves that FQDN to the Private Endpoint IP from connected networks.

Domain names used in this guide: *.{region}.adcproxy.io is the default Sandbox ingress domain. After you link a Sandbox Group, its port FQDNs use *.{region}.azurecontainerapps.io, the Container Apps Environment ingress domain.

Sandbox ingress uses the same Private Endpoint and Private DNS model as an Azure Container Apps Environment. See the Container Apps Private Endpoint concept and Private Endpoint tutorial.

Understand the ingress flow

A Private Endpoint for Sandbox ingress targets a Container Apps Environment configured with mode=Express. It doesn't target an individual Sandbox Group or Sandbox. One Private Endpoint on the Container Apps Environment provides private ingress for ports exposed by Sandboxes in every linked Sandbox Group.

Linking a Sandbox Group to the Container Apps Environment changes ingress only. The Sandbox Group's vnetConnections child resource and each Sandbox's CustomerVnetConnectionName continue to control egress without changes. The Container Apps Environment's VNet and subnet don't affect the Sandbox Group or its Sandboxes. To give a Sandbox outbound access to private resources in your VNet, use a VNet connection.

The recommended order is:

  1. Create the Container Apps Environment with mode=Express.
  2. Create and approve the Private Endpoint.
  3. Disable public network access on the Container Apps Environment.
  4. Link the Sandbox Group after the private ingress path is ready.

This order prevents an ingress interruption for an existing Sandbox Group before it is linked.

Prerequisites

  • An Azure account with an active subscription.
  • The latest Azure CLI and Container Apps extension with preview features enabled: az extension add --name containerapp --upgrade --allow-preview true.
  • An existing Sandbox Group with a Sandbox that exposes an HTTP port.
  • Contributor access to the resource group that contains the Sandbox Group and the resource group where you create the Container Apps Environment and Private Endpoint.
  • A VNet and subnet for the Private Endpoint.
  • Network Contributor access to the VNet and subnet used by the Private Endpoint.
  • Permission to create or link the Azure Private DNS Zone.
  • The Microsoft.App/managedEnvironments/join/action permission on the Container Apps Environment for the identity that links the Sandbox Group.
  • A VM or other test client that can resolve DNS and send HTTPS requests from the VNet.
Irreversible Sandbox Group link

The Sandbox Group's environmentId is set once. After you link the Sandbox Group, you can't unlink it or move it to another Container Apps Environment. To use another Container Apps Environment, create a new Sandbox Group.

Private Endpoints incur Azure Private Link and Container Apps Private Endpoint infrastructure charges. See Private Endpoint billing.

If your network filters DNS names, permit the Container Apps Environment ingress domain, *.{region}.azurecontainerapps.io, before you link the Sandbox Group. Existing Sandbox port FQDNs migrate from the default Sandbox ingress domain, *.{region}.adcproxy.io.

Create a Container Apps Environment with mode=Express

Create the Container Apps Environment with mode=Express and public network access enabled. The Sandbox Group isn't linked yet, so its existing ingress URLs are unchanged.

RESOURCE_GROUP="${ACA_EXAMPLES_RG}"
LOCATION="${ACA_EXAMPLES_REGION}"
ENVIRONMENT_NAME="${ENVIRONMENT_NAME:-sandbox-private-env}"

az containerapp env create \
--name "${ENVIRONMENT_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--location "${LOCATION}" \
--environment-mode Express \
--public-network-access Enabled \
--output none

az containerapp env show \
--name "${ENVIRONMENT_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--query "{mode:properties.environmentMode,state:properties.provisioningState,domain:properties.defaultDomain,publicAccess:properties.publicNetworkAccess}"

Continue when mode is Express, state is Succeeded, and domain contains the Container Apps Environment ingress domain.

Create the Private Endpoint

Create the Private Endpoint before you link the Sandbox Group.

  1. Open the Container Apps Environment in the Azure portal.
  2. Select Networking > Private endpoints.
  3. Select Create.
  4. On Basics, choose the subscription, resource group, region, and Private Endpoint name.
  5. On Resource, confirm the Container Apps Environment and select managedEnvironments for the target subresource.
  6. On Virtual network, choose the VNet and subnet used by your private clients.
  7. On DNS, select Integrate with private DNS zone. Use privatelink.<region>.azurecontainerapps.io.
  8. Select Review + create, then Create.
  9. Wait for the Private Endpoint connection state to become Approved.

Confirm that the Private DNS Zone is linked to the client VNet before you continue.

For Azure CLI steps to create the Private Endpoint, see Use a Private Endpoint with a Container Apps Environment.

Disable public network access on the Container Apps Environment

Run this step only when the Private Endpoint connection is Approved and the Private DNS Zone is linked to the client VNet. Disabling public network access on the Container Apps Environment blocks public ingress and leaves the Private Endpoint as the allowed ingress path.

RESOURCE_GROUP="${ACA_EXAMPLES_RG}"
ENVIRONMENT_NAME="${ENVIRONMENT_NAME:-sandbox-private-env}"

az containerapp env update \
--name "${ENVIRONMENT_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--public-network-access Disabled \
--output none

az containerapp env show \
--name "${ENVIRONMENT_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--query "{state:properties.provisioningState,publicAccess:properties.publicNetworkAccess}"

Continue when state is Succeeded and publicAccess is Disabled.

Link the Sandbox Group after the Container Apps Environment's private ingress path is ready. This asynchronous operation sets properties.environmentId, properties.defaultDomain, and properties.publicNetworkAccess, and migrates existing Sandbox port FQDNs from the default Sandbox ingress domain to the Container Apps Environment ingress domain.

The poller follows the Azure-AsyncOperation response header every 10 seconds and stops after 15 minutes.

SUBSCRIPTION_ID="$(az account show --query id -o tsv)"
RESOURCE_GROUP="${ACA_EXAMPLES_RG}"
SANDBOX_GROUP="${ACA_EXAMPLES_GROUP}"
ENVIRONMENT_NAME="${ENVIRONMENT_NAME:-sandbox-private-env}"
SBG_API_VERSION="2026-02-01-preview"
ARM_RESOURCE="https://management.azure.com/"
ENVIRONMENT_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.App/managedEnvironments/${ENVIRONMENT_NAME}"
SANDBOX_GROUP_ID="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.App/sandboxGroups/${SANDBOX_GROUP}"
ARM_TOKEN="$(az account get-access-token --resource "${ARM_RESOURCE}" --query accessToken -o tsv)"
HEADERS_FILE="$(mktemp)"
RESPONSE_FILE="$(mktemp)"
trap 'rm -f "${HEADERS_FILE}" "${RESPONSE_FILE}"' EXIT

HTTP_STATUS="$(curl --silent --show-error \
--request PATCH \
--url "https://management.azure.com${SANDBOX_GROUP_ID}?api-version=${SBG_API_VERSION}" \
--header "Authorization: Bearer ${ARM_TOKEN}" \
--header "Content-Type: application/merge-patch+json" \
--data "{\"properties\":{\"environmentId\":\"${ENVIRONMENT_ID}\"}}" \
--dump-header "${HEADERS_FILE}" \
--output "${RESPONSE_FILE}" \
--write-out "%{http_code}")"

if (( HTTP_STATUS < 200 || HTTP_STATUS >= 300 )); then
cat "${RESPONSE_FILE}" >&2
exit 1
fi

ASYNC_URL="$(awk 'tolower($1) == "azure-asyncoperation:" { gsub(/\r/, "", $2); print $2 }' "${HEADERS_FILE}")"
if [[ -z "${ASYNC_URL}" ]]; then
echo "Sandbox Group link completed synchronously."
else
DEADLINE=$((SECONDS + 900))
LINK_COMPLETED=false
while (( SECONDS < DEADLINE )); do
OPERATION_STATUS="$(az rest \
--method get \
--url "${ASYNC_URL}" \
--resource "${ARM_RESOURCE}" \
--query status \
--output tsv)"

case "${OPERATION_STATUS}" in
Succeeded)
echo "Sandbox Group link completed."
LINK_COMPLETED=true
break
;;
Failed|Canceled)
echo "Sandbox Group link ${OPERATION_STATUS}." >&2
exit 1
;;
*)
echo "Sandbox Group link status: ${OPERATION_STATUS:-InProgress}"
sleep 10
;;
esac
done

if [[ "${LINK_COMPLETED}" != true ]]; then
echo "Timed out after 15 minutes waiting for the Sandbox Group link." >&2
exit 1
fi
fi

Confirm the migrated Sandbox port FQDN

Use the ACA CLI to get the current port FQDN from the Sandbox. The migrated FQDN uses the Container Apps Environment ingress domain, *.{region}.azurecontainerapps.io.

SANDBOX_GROUP="${ACA_EXAMPLES_GROUP}"
SANDBOX_NAME="${SANDBOX_NAME:-demo-port}"

SANDBOX_PORT_FQDN="$(aca sandbox get \
--group "${SANDBOX_GROUP}" \
-l "name=${SANDBOX_NAME}" \
--query portUrl.fqdn \
--output tsv)"

echo "Migrated port FQDN: ${SANDBOX_PORT_FQDN}"
export SANDBOX_PORT_FQDN

The migration is transparent to clients that use the current port URL returned by the service. Update DNS filters and domain allowlists that only permit the default Sandbox ingress domain, *.{region}.adcproxy.io.

Verify Private Endpoint ingress

The Private DNS Zone contains an A record for the Container Apps Environment ingress domain label that points to the Private Endpoint IP address.

Run the test from a VM or client in the VNet that contains the Private Endpoint, or from a connected network that uses the same Private DNS path.

: "${SANDBOX_PORT_FQDN:?Run the migrated FQDN step first}"

nslookup "${SANDBOX_PORT_FQDN}"
curl -i --max-time 40 "https://${SANDBOX_PORT_FQDN}" | sed -n '1,20p'

DNS should return the Private Endpoint IP, and the HTTPS request should succeed. The same FQDN remains blocked from the public internet while public network access is disabled on the Container Apps Environment.

If the VNet uses a custom DNS server, conditionally forward privatelink.<region>.azurecontainerapps.io to Azure DNS at 168.63.129.16. For on-premises clients, resolve the zone through Azure DNS Private Resolver or another DNS server that can query the linked Private DNS Zone.

Remove Private Endpoint ingress

To restore public ingress:

  1. Set public network access on the Container Apps Environment to Enabled.
  2. Confirm the existing Sandbox port FQDN works from the public internet.
  3. Delete the Private Endpoint and its DNS Zone Group if they are no longer needed.

Re-enabling public network access doesn't move port FQDNs back to the default Sandbox ingress domain. Because the Sandbox Group remains linked to the Container Apps Environment, its existing port FQDNs continue to use the Container Apps Environment ingress domain and work through public ingress.

You can't unlink the Sandbox Group from the Container Apps Environment. Before you delete the Container Apps Environment, delete its Private Endpoints and every linked Sandbox Group.

Troubleshooting

SymptomCause and action
The Sandbox Group link returns a set-once errorThe Sandbox Group is already linked. Create a new Sandbox Group if you need another Container Apps Environment.
The link poller times outGet the operation URL from the Azure-AsyncOperation response header and check its status. Don't retry the link with another Container Apps Environment.
The port FQDN still uses the default Sandbox ingress domain, *.{region}.adcproxy.ioThe migration operation isn't complete. Check the async operation, then run aca sandbox get again.
DNS returns a public IPConfirm the Private DNS Zone name, VNet link, A record, and DNS Zone Group.
DNS fails after the FQDN migrationPermit the Container Apps Environment ingress domain, *.{region}.azurecontainerapps.io, in DNS filters and domain allowlists.
The Private Endpoint connection is pendingApprove the connection from the Container Apps Environment's Private Endpoint connections page.
The private request returns 403 or times outConfirm the client is in or connected to the VNet, resolves the FQDN to the private IP, and can reach the Private Endpoint subnet on HTTPS.

For general endpoint diagnostics, see Private Endpoint troubleshooting.