The cleanest way to ship a security scan on every pull request is to wire it into the same pipeline that runs your tests. According to Acunetix's published integration example, the workflow uses Docker for staging, a small bash script for scan orchestration, and GitHub's pull request trigger as the entry point.
Here's how each piece fits together, and where teams usually need to pay attention.
At a glance
- Trigger: pull request on a GitHub repo.
- Stage: Docker container built and run from a Dockerfile committed to the repository.
- Orchestration: a bash script that calls Acunetix's API to create a target, start a scan, and wait for completion.
- Pass rule: zero vulnerabilities found.
- Fail rule: one or more vulnerabilities found, which blocks the merge.
How the cycle runs
The integration spins up the same web application on Docker that production would run, by copying source files into a build directory and launching a container image. A health check confirms the staging container responds with a 200 OK before the scan starts.
Once the staging environment is ready, the bash script calls the Acunetix API to register the staging URL as a scan target and launch a scan against it. The script polls until the scan reaches a terminal status, then counts the vulnerabilities. If the count is zero, the pipeline passes. If it's non-zero, the script exits with a failure code and the pull request check fails.
Cleanup runs at the end of every cycle: the script deletes the scan target, stops and removes the staging container, and removes the build directory from the Docker host.
What the source tree looks like
The published example uses three artifacts: application code under product/, the Dockerfile that builds the staging container, and a scripts/ directory holding the bash orchestration script. The Dockerfile in the example is a minimal php:7.3.28-apache image that copies index.php into the web root.
The integration does not require a complex repository. A small repo with one application file, one Dockerfile, and one script reproduces the pipeline end to end. Teams can swap the example for their own stack without changing the script's structure.
Where teams usually trip up
The first trip-up is environment parity. If the staging container runs a different web server, PHP version, or reverse-proxy setup than production, the scan reports on the wrong environment. Pinning versions in the Dockerfile and reusing that Dockerfile for staging and production fixes most of this.
The second is treating the scan result as binary. A scan that finds zero issues is a pass. A scan that finds one issue is sometimes a false positive. A scan that finds dozens of issues is sometimes a misconfigured staging environment that has been silently broken. Treat the count as a baseline, not a verdict.
The third is cleanup. The script needs to delete the scan target, stop and remove the container, and remove the build directory at the end. Without this, Docker hosts accumulate stale containers over time.
Our analysis
The integration's real value isn't the scan itself; it's the feedback loop. A scan that runs on every pull request surfaces findings while the code change is still small, which is when remediation is cheapest. The same scan run quarterly becomes a remediation backlog no one wants to address.
The second benefit is that this loop runs without a security engineer in the loop. A developer submitting a pull request gets scan feedback in the same place they get unit test feedback. That shifts security from a separate process to part of the build.
Who should care
| Team | Why it matters | What to watch |
|---|---|---|
| Developers | Get security feedback without leaving the pull request workflow; no separate security review step | False positives that block legitimate merges; treat findings as a starting point |
| Security engineers | Automated baseline coverage on every change; no manual scan scheduling | Scan scope matching production; drift between staging and prod configs |
| DevOps | Pipeline integration without new infrastructure; cleanup is scripted | Docker host capacity if many PRs run concurrently; secret management for API keys |
A worked example
Say you maintain a Node.js service for an internal tool and want to add the same pattern. Replace the php:7.3.28-apache image with a node:18-alpine image, point the bash script at your staging URL, and commit the same three files to a new branch. Open a pull request. The first run will likely flag at least one finding—a missing security header or an exposed configuration file—because the baseline has never been scanned. That's the finding you would have missed entirely in a quarterly scan, and it's now blocking the merge of an unrelated feature, which is the point.
Bottom line
Run the scan on every pull request, fail the merge on any finding, and treat the count as a baseline. The discipline is the product.
Key takeaways
- The pipeline requires only three committed artifacts: application code, a Dockerfile, and an orchestration script.
- Environment parity between staging and production is the single biggest risk to scan accuracy.
- Cleanup is mandatory; without it, Docker hosts accumulate stale containers.
- The scan result is a baseline, not a verdict—investigate spikes rather than treating every non-zero count as a critical failure.
Frequently asked questions
What does the Acunetix GitHub integration do on each pull request?
According to Acunetix's example, the integration spins up a Docker staging container, runs a full Acunetix scan against it, and fails the build if any vulnerabilities are found.
Where does the staging environment come from?
The integration builds a Docker image from a Dockerfile committed to the repository and runs the container on a Docker host before scanning.
How does the integration clean up after itself?
The bash script deletes the scan target via the Acunetix API, stops and removes the staging container, and removes the build folder from the Docker host.
Can the example be reproduced with a different tech stack?
Yes. The pattern requires only a Dockerfile for the app, a bash script for scan orchestration, and a GitHub repository with pull requests enabled.
Why run the scan on every pull request instead of periodically?
Findings caught at the pull request stage are cheaper to fix than findings caught later, and they prevent security debt from accumulating across the codebase. **END**