Deploying with Docker Compose
In SonarQube Server 2026.5, deploy the Agent Orchestrator, the agent runtimes, and Vortex analysis with Docker Compose, for environments that do not run Kubernetes.
Docker Compose is the supported way to deploy the AI agents outside Kubernetes. You run the components on a container host alongside your existing SonarQube Server deployment, and point each side at the other.
The Agent Orchestrator, each agent runtime, and Vortex analysis run as long-lived services. Each runtime service handles one job at a time, which fixes your job capacity. Size the host for the number of concurrent jobs you want, and add runtime services to raise that number.
The Docker Compose deployment outlined on this page covers only the agent components. Your SonarQube Server instance, its database, and your storage backend are expected to already exist. Point the components at them rather than running them as part of this deployment.
Services to define
Agent Orchestrator
Coordinates jobs. Connects to the SonarQube Server database and to storage.
Hunter Agent runtime
Runs Hunter Agent jobs. Only if you use the Hunter Agent.
Remediation Agent runtime
Runs Remediation Agent jobs. Only if you use the Remediation Agent.
Vortex analysis
Verifies proposed fixes. Required by the Remediation Agent.
Egress proxy
Restricts what the runtime containers can reach.
Signing key derivation
A one-shot service that derives the per-component request-signing keys on every start.
The key derivation service does work that the Helm chart does for you on Kubernetes, which is why you define it yourself here. Have each component depend on it with service_completed_successfully: a failed derivation then stops the start, rather than leaving you with components that cannot authenticate to each other.
Planning the deployment
Settle these before you write the Compose file, because two of them determine how the services are addressed.
Where SonarQube Server runs
The components and SonarQube Server call each other in both directions, and each needs an address for the other that resolves from where it runs. The Example Compose file runs SonarQube Server on the container host: the containers reach it at its host name, and SonarQube Server reaches the Orchestrator's and Vortex analysis's published ports at localhost. Only the Orchestrator and Vortex analysis are ever published. The runtimes are not. If SonarQube Server runs on another host, see SonarQube Server on its own host.
SonarQube Server to the Orchestrator
Triggers jobs, and polls component health for the System page.
SonarQube Server to Vortex analysis
Polls its health, and requests analyses.
The Orchestrator to SonarQube Server
Reads project data, requests short-lived repository credentials, and publishes results.
The Orchestrator to the SonarQube Server database
Tracks job state.
Vortex analysis to SonarQube Server
Reads what it needs to analyze.
The Remediation Agent runtime to SonarQube Server, through the egress proxy
Fetches rule descriptions, and verifies fixes.
Storage backend
Object storage is the default and the simpler of the two here, because the components exchange artifacts through short-lived presigned URLs rather than a shared mount. The Example Compose file below uses it. For a shared filesystem instead, see Using a shared filesystem instead of object storage.
Private certificate authority
If SonarQube Server serves a certificate from your own authority, or outbound traffic passes through a TLS-inspecting proxy, every component needs to trust that authority. See Trusting a private certificate authority.
How the components authenticate
The components do not authenticate to each other with tokens. You supply one shared secret, and each component signs and verifies requests with a key derived from it. A component never holds the keys for a hop it does not take part in.
Generate the secret once, and keep it as securely as any other credential:
The secret must be at least 32 characters. SonarQube Server disables signature verification on a shorter one without reporting an error, which leaves the agentic endpoints unauthenticated.
Deriving the keys
The Agent Orchestrator image ships the derivation script at /derive-keys.sh. Use it rather than deriving the keys another way. Kubernetes deployments derive with the same script, which means one secret produces the same keys on both, and moving between them needs no re-keying.
Call it once per start, writing each component's keys into a volume of its own:
Each component then mounts only its own volume, read-only. The one-shot service in the Example Compose file below shows the full set of labels.
SonarQube Server
The secret itself, and agentic-shared
Verifies inbound calls from the Orchestrator, Vortex analysis, and the Remediation Agent runtime. Signs its own calls to the Orchestrator.
Agent Orchestrator
agentic-shared, orchestrator-to-hunter, orchestrator-to-remediation, hunter-to-orchestrator, remediation-to-orchestrator, orchestrator-job-capability
Verifies inbound calls from SonarQube Server and from both runtimes. Signs its own calls to SonarQube Server and its job pushes to each runtime. Issues each job's upload capability.
Hunter Agent runtime
orchestrator-to-hunter, hunter-to-orchestrator
Verifies job pushes from the Orchestrator, and signs its own requests back to it.
Remediation Agent runtime
orchestrator-to-remediation, remediation-to-orchestrator, remediation-to-sqs
Verifies job pushes from the Orchestrator, signs its own requests back to it, and signs its direct calls to SonarQube Server.
Vortex analysis
agentic-shared
Signs its calls to SonarQube Server, and verifies SonarQube Server's calls to it.
Never mount the secret itself, or the orchestrator-job-capability key, into a runtime container. Runtimes run code influenced by a large language model, and either one would let a job mint credentials for hops it has no part in.
SonarQube Server needs two of these files. See Handing the keys to SonarQube Server for how to derive them, and Pointing SonarQube Server at the components for the properties that point at them.
Rotating the secret
Recreate the containers after you change the secret:
Derivation runs again on docker compose up, but nothing else about the running containers changes, and Compose leaves them holding the keys derived from the old secret. They keep signing with a key no other component has, which surfaces as unexplained 401 responses rather than as a configuration error.
To rotate without downtime, put the previous secret in a file named after the secret file with a .previous suffix before you derive. The derivation writes a matching .previous key beside each key, and a verifier accepts both generations, which lets you restart components one at a time. Remove the previous generation once every container has been recreated.
SonarQube Server derives its own copy of the keys, so update it as well, see Handing the keys to SonarQube Server.
Pointing SonarQube Server at the components
An environment variable overrides a SonarQube Server property only when that property is a built-in system property, or is already present in conf/sonar.properties. None of the agentic properties are built-in system properties, which means setting only the environment variable has no effect: SonarQube Server starts as though the components were never deployed. Declare each property in conf/sonar.properties first.
Use the published addresses from Planning the deployment, not container names. Container names resolve only on the container host's own Compose networks:
These are the ports published by this bundle; use ORCHESTRATOR_PUBLISH_PORT/VORTEX_PUBLISH_PORT from .env instead if you set them.
On a container image, the configuration directory is read-only at container start. Mount a complete file over it instead. Declaring a property with an empty value is enough to make it overridable, which is what lets you keep addresses and credentials in your environment rather than in the mounted file. On a ZIP installation, edit conf/sonar.properties in place.
Database
This bundle points the containers at the same external database you passed to generate.py. Point your SonarQube at it too, if it isn't already:
The orchestrator connects with the same sslmode=verify-full: the database must serve TLS with a certificate valid for db.example.com. Your SonarQube validates it against its JVM truststore, so the CA behind it must be in the JDK's default cacerts or in the -Djavax.net.ssl.trustStore you set in sonar.web.javaAdditionalOpts and sonar.ce.javaAdditionalOpts.
Network reachability
The containers expect to reach your SonarQube at http://sonarqube.example.com:9000. If that isn't where your install actually listens, regenerate this bundle with --sonarqube-external-host/--sonarqube-external-port/--sonarqube-external-scheme set correctly, or make sure SonarQube is bound and reachable there (a firewall or a bind to 127.0.0.1 only are the usual culprits).
Storage
Set the same storage properties this bundle's containers use — sonar.agentic.storage.* and sonar.agentic.orchestrator.storage.* must point at the same backend and credentials/paths on both sides. Copy the values from this bundle's docker-compose.yaml/.env for the storage mode you generated with.
TLS trust
TLS is disabled in this bundle; every hop is plain HTTP. See Traffic between components.
For the full list of settings on both sides, see Configuration reference.
Example Compose file
The example is a bundle produced by the Compose generator, which matches what the Helm chart ships for 2026.5.0. It was generated for the setup this page describes: an existing SonarQube Server (--edition none), its external PostgreSQL, S3 storage, and all three components, with TLS off; see Traffic between components. The command that produced it:
The host names, bucket, and credentials in the example are placeholders, and the signing secret is redacted. Run the generator with your own values rather than editing the generated files.
Environment file
Compose reads .env from the same directory automatically. Keep it out of version control, and restrict who can read it: it holds the signing secret and the storage credentials.
Replace the AGENTIC_SIGNING_SECRET placeholder with a secret you generate as described in How the components authenticate. The placeholder text is long enough to pass the 32-character check, so the deployment starts with it if you forget.
Values you set here and in docker-compose.yaml become plain container environment variables. Anyone with Docker access on the host can read them with docker inspect or docker compose config. Treat access to the container host as access to these credentials.
Restricting outbound access
The runtime containers sit on networks with no route off the host. Everything they reach, they reach through the egress proxy, which is the one place you enforce and audit what a job can do.
The proxy allows exactly these things, and denies the rest:
Your LLM providers
Either runtime
The domains in AGENTIC_LLM_ALLOWED_DOMAINS.
Your storage endpoint
Either runtime
The bucket host in AGENTIC_STORAGE_ALLOWED_DOMAIN, when one is set.
SonarQube Server
The Remediation Agent runtime only, on the proxy's dedicated 3129 listener
Rule descriptions and fix verification.
Upload URL renewal
Either runtime
The Orchestrator, on that one route.
Scoping the SonarQube Server rule to one runtime and one listener matters: the Hunter Agent runtime has no reason to reach SonarQube Server, and both runtimes reach the same proxy, so the rule pairs the listener with the source rather than relying on the port alone.
Rather than a static file, the proxy renders its configuration from a template at container start, so the allowlist comes from environment variables instead of being hand-edited into squid.conf:
Mount both files as shown in the Example Compose file, and set the domains through the egress-proxy service's environment.
Create both files before the first start. A bind mount whose source is missing is created as an empty directory. The proxy then starts with a directory where its configuration should be, and fails with an error that points at Squid rather than at the missing file.
Do not give AGENTIC_LLM_ALLOWED_DOMAINS or AGENTIC_STORAGE_ALLOWED_DOMAIN a leading dot. A leading dot matches every subdomain: .example.com allows any host under that domain rather than the one you intended. The entrypoint script refuses one and exits, rather than starting with an accidental wildcard allow.
Where SonarQube Server is served over HTTPS, the Remediation Agent runtime's calls to it arrive as a CONNECT tunnel, which the proxy cannot see inside, so it can only hold the tunnel to the configured host and port. That leaves the runtime itself to trust the certificate, see Trusting a private certificate authority.
A denied request surfaces inside the agent as a provider failure rather than as a policy error. Check the proxy first when you diagnose one:
Vortex analysis
Define Vortex analysis as a long-lived service alongside the Orchestrator, with access to the shared storage location. It authenticates to SonarQube Server with a signing key derived from the shared AGENTIC_SIGNING_SECRET, not a token. Then point SonarQube Server at it, see Configuration reference.
Allow a slow first start. Vortex analysis runs a JVM analyzer with Node and .NET alongside it, so it reports healthy later than the other components do.
Docker Compose has no dependency validation, so nothing stops you from starting the Remediation Agent runtime without Vortex analysis. If you do, the agent runs in a degraded state and its fixes are not verified. See Vortex analysis for why the Remediation Agent needs it, its storage and lifecycle requirements, and how to confirm it's running.
Running the runtimes sandboxed
Each runtime service must request the sandbox runtime and set its own resource limits. Docker cannot cap a container's disk usage on its own, so the sandbox provides that through a bounded writable overlay, configured when you install it on the host.
Set pids_limit on every runtime. It bounds how many processes a job can create, in code paths a large language model influences.
Do not override runtime to an unsandboxed value. If runsc is not registered on the host, the container refuses to start rather than falling back, which is the intended behavior. Fix the host instead of removing the sandbox.
Size each service's cpus, mem_limit, and the sandbox's writable overlay to match its Kubernetes resource requests and limits. See Capacity planning for the current defaults for the Hunter Agent runtime, the Remediation Agent runtime, and Vortex analysis.
Job timeouts and shutdown
Four settings bound how long a job may run, and they have to stay in order. Three are environment variables on the runtime, carrying a default from its image; the fourth is on the Orchestrator.
SCRIPT_TIMEOUT_SECONDS
The runtime
How long the agent itself may run before the runtime stops it.
43200 (12 hours)
900 (15 minutes)
SHUTDOWN_GRACE_SECONDS
The runtime
How long shutdown waits for an in-flight job to finish.
43800
2100
LIVENESS_JOB_MAX_SECONDS
The runtime
When a job counts as stuck rather than slow, at which point the container is restarted.
46800
2700
sonar.agentic.orchestrator.watcher.hunter-timeout-seconds and .remediation-timeout-seconds
The Orchestrator
How long the Orchestrator waits before marking a job failed.
44400
3600
Keep them in ascending order on each runtime, and keep the Orchestrator's timeout above the runtime's script timeout. A grace period below the script timeout means a job in flight at shutdown is killed rather than finished; an Orchestrator timeout below the script timeout means the Orchestrator gives up on a job that is still working.
Stopping without losing a job
Each component drains on shutdown. The Orchestrator runs a graceful web drain followed by two scheduler shutdown phases (5 minutes and 15 minutes), and each runtime waits for its running job to finish and upload its results.
Compose allows 10 seconds for that by default. Set stop_grace_period above each component's own drain, as the example does:
The runtime values sit just above each image's own graceful-shutdown wait for a running job (44400 seconds for the Hunter Agent, 3600 seconds for the Remediation Agent), so a stop does not kill a job before that wait ends. The Vortex analysis value lets in-flight analyses finish.
Without this, docker compose down, stop, and restart send SIGTERM and then kill the container 10 seconds later, in the middle of the drain. A running job is lost with its results unwritten, and because the runtime is killed before it can report anything, the Orchestrator only fails the job once its own watcher timeout expires, up to 12 hours later for the Hunter Agent.
These are ceilings, not waits. Stopping an idle deployment returns immediately, because the drain completes as soon as there is nothing in flight. Run docker compose ps first if you want to know whether a job is running before you stop.
Network isolation
The Compose deployment models the same security boundaries the Kubernetes network policies do, using separate Docker networks. The important properties to preserve:
The Orchestrator can reach the runtimes
It pushes jobs to them.
SonarQube Server cannot reach the runtimes
The runtimes are untrusted, and nothing in SonarQube Server needs to call them.
The runtimes cannot reach SonarQube Server or the database, except the Remediation Agent runtime's signed calls through the egress proxy
Most jobs ask the Orchestrator for SonarQube Server data; the Remediation Agent runtime calls SonarQube Server to verify fixes and fetch rule descriptions.
The runtimes cannot reach each other
One job must not be able to observe or disturb another.
Only the egress proxy has a route out
This is what constrains where a job can send data.
Two things do that work in the example. First, each runtime sits only on its own network, hunter-jobnet or remediation-jobnet, shared with the Orchestrator for job dispatch and with the egress proxy for its route out. Both are internal: true, so the runtimes have no route off the host and cannot reach each other, and SonarQube Server is on neither. Second, only the Orchestrator and Vortex analysis publish a port.
The runtime services do not authenticate requests to their own endpoints beyond verifying the Orchestrator's signature on a job push. Their protection is network isolation, so these boundaries are a security control rather than tidy organization. Do not publish runtime ports, and do not place the runtimes on a network that other workloads can reach.
Trusting a private certificate authority
If SonarQube Server serves a certificate from your own authority, or outbound traffic passes through a TLS-inspecting proxy, the components have to trust that authority. Put the certificates in a directory on the host and mount it. Each component takes it differently:
Agent Orchestrator
Mount the certificates at /custom-ca. Nothing else is needed.
Agent runtimes
Mount the certificates at /custom-ca. Nothing else is needed.
Vortex analysis
Build a truststore and point the JVM at it.
SonarQube Server
Build a truststore and point the web and compute engine JVMs at it.
Neither Vortex analysis nor SonarQube Server reads /custom-ca, and SonarQube Server's own truststore is read-only inside its image. Add a one-shot service that builds a truststore from the same directory:
Give SonarQube Server the same truststore, through SONAR_WEB_JAVAADDITIONALOPTS and SONAR_CE_JAVAADDITIONALOPTS.
The Example Compose file has no /custom-ca mount, because it uses plain HTTP throughout. When you need one, add ./custom-ca:/custom-ca:ro to the volumes of the Orchestrator and of each runtime service. An empty /custom-ca changes nothing, and each component keeps the trust its image shipped with.
Traffic between components
Traffic between the Orchestrator, the runtimes, and Vortex analysis is unencrypted, on the basis that these components sit together on internal networks on one host and authenticate each request by signature rather than by transport. Traffic that leaves the host does not have to be: put SonarQube Server behind TLS and the components reach it over HTTPS. TLS is disabled in the example; every hop is plain HTTP.
To encrypt the published component ports as well, terminate TLS in front of the Orchestrator and Vortex analysis with a reverse proxy of your own, and give SonarQube Server the issuing authority.
Starting the deployment
Have these files beside your Compose file before the first start:
.env
The values from Environment file.
egress-proxy/squid.conf.template
The egress allowlist template from Restricting outbound access.
egress-proxy/entrypoint.sh
The script that renders the allowlist and starts the proxy, from Restricting outbound access.
The orchestrator stores its state in SonarQube's database and keeps failing its health check until SonarQube has created the schema, so start your SonarQube first, then docker compose up -d. On an upgrade, SonarQube Server must also be migrated before the Orchestrator starts. The Helm chart enforces this with a wait-for-sonarqube init container on the Orchestrator.
Then start the components and confirm each one reports healthy:
Handing the keys to SonarQube Server
Run the commands below on the SonarQube Server host. Compose reads .env on its own, but your shell does not, so set AGENTIC_SIGNING_SECRET in the shell first, to the same value as in .env. The orchestrator image is the one the example uses, sonarsource/sonarqube-agent-orchestrator:2026.5.0.
Every agentic request between SonarQube, the orchestrator, and Vortex is signed. Derive your copy of the shared key from this bundle's AGENTIC_SIGNING_SECRET (in .env) and point SonarQube at it:
instance-secret is the shared secret itself, and every other key derives from it. The derivation runs as root, so make sure both instance-secret and agentic-shared.key are owned by the SonarQube Server user and readable only by it.
Then in sonar.properties:
Apply the rest of the properties from Pointing SonarQube Server at the components, and restart SonarQube Server.
Regenerating this bundle keeps AGENTIC_SIGNING_SECRET, so these keys stay valid. Only after --rotate-secrets redo this step and run docker compose up -d --force-recreate, so SonarQube and the containers derive their keys from the new secret.
Confirming it worked
Check Administration > System, where the Agent Orchestrator, Vortex analysis, and each agent runtime the Orchestrator can see should all report healthy. See Verifying the deployment.
If Vortex stays unhealthy and its health check reports sonarqube-connectivity DOWN: ... Unauthorized, your SonarQube is missing part of sections 3, 5 or 7: check that all three are applied and that SonarQube was restarted since. With TLS, agentic-proxy waits for a healthy Vortex, so it doesn't start either until then.
Sections 3, 5, and 7 are the Orchestrator and Vortex analysis URLs in Pointing SonarQube Server at the components, the signing key in Handing the keys to SonarQube Server, and TLS trust.
Using a shared filesystem instead of object storage
A shared filesystem works too, and is the other backend in Setting up shared storage. It changes three things:
Set the storage type to
FILESYSTEMand a base directory instead of a bucket, in both property namespaces, and on SonarQube Server as well:
Mount the storage in every component, including both runtimes, at the same path. The components exchange artifacts by path in this mode, and they must all resolve a given path to the same file
Add a one-shot service that makes the directory writable by every component, which each run as a different user with no owner or group in common
The trade-off is that the runtimes hold a mount rather than a short-lived presigned URL, and the path has to match everywhere. Object storage avoids both.
SonarQube Server on its own host
The example runs SonarQube Server on the container host. The components already reach SonarQube Server and its database by host name (sonarqube.example.com and db.example.com), so that side works unchanged from another host. On SonarQube Server, replace localhost in the Orchestrator and Vortex analysis URLs from Pointing SonarQube Server at the components with the container host's address. Both sides need names that resolve from where they run and a network path on those ports.
The agents cannot run from a ZIP installation, because they need a container runtime for the per-job sandbox. SonarQube Server itself can, and this deployment works against it unchanged: set the agentic properties in its own conf/sonar.properties and place the secret file and the agentic-shared key on that host.
Next step
Confirm the deployment works, see Verifying the deployment.
Related pages
Last updated
Was this helpful?

