Custom integrations
APD is designed to be extended. This page describes how domain.yaml is delivered to ADS by writing simple Gradle tasks in your build.gradle file. No additional tooling is required, as any delivery mechanism your environment supports can be wired in directly.
When using the image-based deployment model, the authorization domain is compiled and baked into the ADS container image at build time. This gives you a fully immutable, versioned artifact, but it means every policy change requires a new image build and a redeployment.
For teams that need to update policies without rebuilding the image (for example, to push a hotfix quickly or to share a single base ADS image across multiple environments), a custom integration is a better fit. The idea is simple: compile domain.yaml with APD and then deliver it to wherever ADS is reading it from, separately from the image.
ADS supports runtime domain updates without a restart. When enabled, it polls the configured domain.yaml location for changes and reloads the domain automatically. See the ADS documentation on runtime domain updates for how to configure polling.
How it works
After buildAuthzDomain runs, the compiled domain.yaml is available through the task's output property tasks.buildAuthzDomain.domainFile. Using this property directly in your custom task is preferred over hardcoding the path, as it automatically reflects any configuration changes and establishes a proper Gradle task dependency.
A custom Gradle task that depends on buildAuthzDomain can deliver the file to the target environment using any mechanism: SCP, kubectl, a REST API, or anything else available in your pipeline.
tasks.register('deployDomain') {
dependsOn buildAuthzDomain
doLast {
def domainFile = tasks.buildAuthzDomain.domainFile
// deliver domainFile to your target environment
}
}
The sections below show the most common delivery patterns.
Run ./gradlew test before any deployment task to verify the authorization domain is correct before pushing it to an environment.
SCP to a server
Use a Gradle Exec task to copy domain.yaml to a remote host over SSH. The remote path should match the location ADS is configured to read from.
tasks.register('deployDomainViaScp', Exec) {
dependsOn buildAuthzDomain
commandLine 'scp',
tasks.buildAuthzDomain.domainFile.absolutePath,
'user@ads-host.example.com:/opt/ads/domain/domain.yaml'
}
If ADS is configured to poll that path, it will pick up the updated domain automatically. Otherwise, restart the ADS process after the copy.
Kubernetes
PVC volume mount
A PersistentVolumeClaim (PVC) is a piece of shared storage in Kubernetes that multiple pods can access at the same time. If all ADS pods mount the same PVC at the domain path, placing a new domain.yaml on that storage makes it visible to every ADS instance at once. Combined with ADS polling, this means policies update across all running instances without a restart or redeployment.
The challenge is that ADS itself will typically have read-only access to the PVC as it only needs to read the file, not write to it. To deliver a new domain.yaml, a separate process needs write access to that same storage. A practical approach is a short-lived Kubernetes Job: a temporary container that mounts the PVC with write permissions, copies the new domain.yaml onto the volume, and then exits. The CI/CD pipeline creates this Job as part of the deployment step and waits for it to finish before proceeding.
tasks.register('deployDomainToK8s', Exec) {
dependsOn buildAuthzDomain
commandLine 'kubectl', 'cp',
tasks.buildAuthzDomain.domainFile.absolutePath,
"${System.getenv('ADS_NAMESPACE')}/${System.getenv('ADS_POD')}:/opt/ads/domain/domain.yaml"
}
Run it with the target namespace and pod name set as environment variables:
ADS_NAMESPACE=production ADS_POD=ads-7d9f4b-xkp2r ./gradlew deployDomainToK8s
Resolve the pod name dynamically in your pipeline to avoid hardcoding it:
export ADS_POD=$(kubectl get pod -n production -l app=ads -o jsonpath='{.items[0].metadata.name}')
ADS_NAMESPACE=production ./gradlew deployDomainToK8s
Sidecar pattern
In Kubernetes, a sidecar is a small helper container that runs inside the same pod as the main application. It shares the pod's local storage and network, but handles a specific supporting task: receiving new domain files and making them available to ADS.
The idea is that the sidecar and ADS share a small local folder inside the pod (an emptyDir volume). When a new domain.yaml is sent to the sidecar, it writes the file to that shared folder. ADS reads from the same folder and, with polling enabled, picks up the change automatically. Neither ADS nor the sidecar needs to be restarted.
This pattern decouples domain delivery from ADS itself: ADS never needs direct network access to receive updates, and the delivery mechanism can be swapped out by changing the sidecar without touching the ADS configuration.
Two common sidecar designs:
- REST API sidecar: The simpler option. The sidecar exposes an HTTP endpoint that accepts a
domain.yamlupload. The Gradle task POSTs the compiled file to that endpoint (using the REST API pattern described below), and the sidecar writes it to the shared folder. This approach is stateless and straightforward but provides no versioning or rollback. This sidecar is not provided by Axiomatics or APD. Instead, it is a component you build and deploy as part of your own platform. - MongoDB-backed sidecar: The more capable option. The sidecar stores each domain version as a document in MongoDB. The Gradle task sends the new
domain.yamlto MongoDB (directly or via the sidecar's API), and the sidecar detects the new version and writes it to the shared folder. This adds versioning and rollback capabilities at the cost of a MongoDB dependency. Choose this option if your team already runs MongoDB and needs the ability to roll back to a previous policy version. As with the REST API sidecar, this is a custom component built and operated by your team.
REST API
Generic REST endpoint
To POST domain.yaml to an arbitrary REST endpoint, use a doLast block with a standard HTTP call:
tasks.register('deployDomainViaRest') {
dependsOn buildAuthzDomain
doLast {
def domainFile = tasks.buildAuthzDomain.domainFile
def url = new URL(System.getenv('DOMAIN_API_URL'))
def connection = url.openConnection() as HttpURLConnection
connection.requestMethod = 'PUT'
connection.setRequestProperty('Content-Type', 'application/yaml')
connection.setRequestProperty('Authorization', "Bearer ${System.getenv('DOMAIN_API_TOKEN')}")
connection.doOutput = true
connection.outputStream.write(domainFile.bytes)
def code = connection.responseCode
if (code < 200 || code >= 300) {
throw new GradleException("Failed to upload domain.yaml: HTTP $code")
}
logger.lifecycle("domain.yaml delivered successfully (HTTP $code)")
}
}
Set the target URL and credentials as environment variables before running:
DOMAIN_API_URL=https://my-domain-service.example.com/domain \
DOMAIN_API_TOKEN=<token> \
./gradlew deployDomainViaRest
Axiomatics Authorization Hub API
If your ADS instances are managed by Axiomatics Authorization Hub, pushing domain.yaml to the control plane is handled by the built-in hub repository integration, which also manages attributes and attribute connectors. See APD integration with Authorization Hub for setup and the available push tasks.