Connect Akto with Cloudflare Container
(Traffic Connector Worker on CloudFlare sending traffic data to IngestData API in CloudFlare Container to enable API discovery)
Cloudflare is a global network security platform that provides CDN, DDoS protection, and API security services. Integrating Cloudflare with Akto will enable automatic discovery of all APIs passing through your Cloudflare infrastructure, helping you maintain continuous visibility and protection of your edge-distributed APIs.

To connect Akto with Cloudflare, follow these steps -
Step 1: Deploy the Akto Mini-Runtime-Service as Cloudflare Container
Before configuring the Cloudflare Worker Traffic Connector, you need to deploy the Akto Mini-Runtime Service.
The Akto-Mini-Runtime-Service is a container that runs on Cloudflare and is responsible for receiving traffic logs from the Cloudflare Worker.
Configure image name of Akto Mini-Runtime Service.
Pre-requisites
Make Cloudflare Account . Buy Workers Paid Plan to deploy containers through worker.
Go to terminal of your system and configure to access cloud flare account by exporting cloud flare API token.
You need to create Account API Token by visiting
https://dash.cloudflare.com/<ID>/api-tokens
Create token with below permissions : Containers:Edit, Queues:Edit, Workers R2 Storage:Edit, Workers Tail:Read, Workers KV Storage:Edit, Workers Scripts:Edit, Workers Scripts:Read, Account Settings:Read
Container deployment
Create
wrangler.jsonc
file with these contents -
{
"name": "akto-mini-runtime-1",
"account_id": "your id",
"compatibility_date": "2025-07-11",
"container": {
"image": "mrs:testing"
}
}
Push Akto container to your registry (steps mentioned on Cloudflare docs)
docker pull --platform linux/amd64 aktosecurity/mini-runtime-service:latest
docker tag aktosecurity/mini-runtime-service:latest mrs:testing
wrangler containers push mrs:testing
Confirm the image on cloudflare registry. You will see similar below output :
Login Succeeded The push refers to repository [registry.cloudflare.com/<ID>/akto-mini-runtime]
This is the path of image to be configured in wrangler.jsonc - registry.cloudflare.com/<ID>/akto-mini-runtime:latest
Deploy Akto Traffic Collector (worker)
Create a new Cloudflare project.
In
wrangler.jsonc
file of the workder, add the docker image address
"containers": [
{
"class_name": "MyContainer",
"image": "registry.cloudflare.com/<ID>/mrs:testing",
"max_instances": 10,
"instance_type": "standard", // you might need bigger instances here
"name": "hello-containers"
}
]
Below changes in src/index.ts to connect the Akto Mini-Runtime Service with Cloudflare Worker and to call the API endpoint in the docker image.
import { Container, loadBalance, getContainer, getRandom } from "@cloudflare/containers";
import { Hono } from "hono";
const INSTANCE_COUNT = 1;
export class MyContainer extends Container {
// Port the container listens on (default: 8080)
defaultPort = 8080;
// Time before container sleeps due to inactivity (default: 30s)
sleepAfter = "2h";
// Environment variables passed to the container
envVars = {
MESSAGE: "I was passed in via the container class!",
AKTO_LOG_LEVEL: "DEBUG",
DATABASE_ABSTRACTOR_SERVICE_TOKEN: "<your-database-abstractor-service-token>",
AKTO_TRAFFIC_QUEUE_THRESHOLD: "100",
AKTO_INACTIVE_QUEUE_PROCESSING_TIME: "5000",
AKTO_TRAFFIC_PROCESSING_JOB_INTERVAL: "10",
AKTO_CONFIG_NAME: "STAGING",
RUNTIME_MODE: "HYBRID"
};
// Optional lifecycle hooks
override onStart() {
console.log("Container successfully started");
}
override onStop() {
console.log("Container successfully shut down");
}
override onError(error: unknown) {
console.log("Container error:", error);
}
}
// Create Hono app with proper typing for Cloudflare Workers
const app = new Hono<{
Bindings: { MY_CONTAINER: DurableObjectNamespace<MyContainer> };
}>();
// Home route with available endpoints
app.get("/", (c) => {
return c.text(
"Available endpoints:\n" +
"GET /container/<ID> - Start a container for each ID with a 2m timeout\n" +
"GET /lb - Load balance requests over multiple containers\n" +
"GET /error - Start a container that errors (demonstrates error handling)\n" +
"GET /singleton - Get a single specific container instance",
);
});
// Route requests to a specific container using the container ID
app.get("/container/:id", async (c) => {
const id = c.req.param("id");
const containerId = c.env.MY_CONTAINER.idFromName(`/container/${id}`);
const container = c.env.MY_CONTAINER.get(containerId);
return await container.fetch(c.req.raw);
});
// Demonstrate error handling - this route forces a panic in the container
app.get("/error", async (c) => {
const container = getContainer(c.env.MY_CONTAINER, "error-test");
return await container.fetch(c.req.raw);
});
// Load balance requests across multiple containers
app.get("/lb", async (c) => {
const container = await loadBalance(c.env.MY_CONTAINER, 3);
return await container.fetch(c.req.raw);
});
// Get a single container instance (singleton pattern)
app.post("/api/ingestData", async (c) => {
//const id = 1;
const containerInstance = getRandom(c.env.MY_CONTAINER, INSTANCE_COUNT);
const containerId = c.env.MY_CONTAINER.idFromName(`/container/${containerInstance}`);
const container = c.env.MY_CONTAINER.get(containerId);
return await container.fetch(c.req.raw);
});
export default app;
/api/ingestData
- Is the endpoint in our docker image to be called from container in cloud flare
Now deploy container to cloud flare with command - wrangler deploy
After successful deployment, you will see the URL of your deployed container in the terminal output. It will look something like this:
https://super-resonance-827f.billing-53a.workers.dev
Step 2: Set Up Your Cloudflare Worker Script
Navigate to the Cloudflare Dashboard and select your account.
Go to Workers & Pages.
Click Create and choose Worker.
Click the Hello World button and deploy it.
Click Edit code and replace the default script with the following example:
export default {
async fetch(request, env, ctx) {
const [reqForFetch, reqForCollector] = await duplicateRequest(request); // At the starting of your fetch method
const backendResponse = await fetch(reqForFetch);
return collectTraffic(reqForCollector, backendResponse, env, ctx); // just after getting response
},
};
async function duplicateRequest(request) {
if (!request.body) {
return [request, request.clone()];
}
const [stream1, stream2] = request.body.tee();
const req1 = new Request(request, { body: stream1 });
const req2 = new Request(request, { body: stream2 });
return [req1, req2];
}
function collectTraffic(request, backendResponse, env, ctx) {
const contentType = (request.headers.get("content-type") || "").toLowerCase();
const isAllowed = isAllowedContentType(contentType);
const shouldCapture = isAllowed && isValidStatus(backendResponse.status);
if (!shouldCapture) return backendResponse;
// Split response stream
let responseForClient = backendResponse;
let responseForLogging = null;
if (backendResponse.body) {
const [respStream1, respStream2] = backendResponse.body.tee();
// Return one response to client
responseForClient = new Response(respStream1, {
headers: backendResponse.headers,
status: backendResponse.status,
statusText: backendResponse.statusText
});
// Keep the other for logging
responseForLogging = respStream2;
}
ctx.waitUntil((async () => {
let requestBody = "";
if (request.body) requestBody = await streamToString(request.body);
let responseBody = "";
if (responseForLogging) responseBody = await streamToString(responseForLogging);
await sendToAkto(request, requestBody, backendResponse, responseBody, env);
})());
return responseForClient;
}
function isAllowedContentType(contentType) {
const allowedTypes = [
"application/json",
"application/xml",
"text/xml",
"application/grpc",
"application/x-www-form-urlencoded",
"application/soap+xml"
];
return allowedTypes.some(type => contentType.includes(type));
}
function isValidStatus(status) {
return (status >= 200 && status < 300) || [301, 302, 304].includes(status);
}
async function streamToString(stream) {
const reader = stream.getReader();
const decoder = new TextDecoder();
let result = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
result += decoder.decode(value, { stream: true });
}
return result;
}
async function sendToAkto(request, requestBody, response, responseBody, env) {
const aktoAPI = "https://<your-ingestion-service-address>/api/ingestData";
const logs = generateLog(request, requestBody, response, responseBody);
const aktoRequest = new Request(aktoAPI, {
method: "POST",
body: logs,
headers: { "Content-Type": "application/json", "x-api-key": "<YOUR_AKTO_API_KEY>" },
});
const aktoResponse = await env.data_injection_worker.fetch(aktoRequest);
if (aktoResponse.status === 400) {
console.error(`Akto response: ${aktoResponse.status} ${aktoResponse.statusText}, Body: ${await aktoResponse.text()}`);
}
}
function generateLog(req, requestBody, res, responseBody) {
const url = new URL(req.url);
const value = {
path: url.pathname,
requestHeaders: JSON.stringify(Object.fromEntries(req.headers)),
responseHeaders: JSON.stringify(Object.fromEntries(res.headers)),
method: req.method,
requestPayload: requestBody,
responsePayload: responseBody,
ip: req.headers.get("x-forwarded-for") || req.headers.get("cf-connecting-ip") || req.headers.get("x-real-ip") || "",
time: Math.round(Date.now() / 1000).toString(),
statusCode: res.status.toString(),
type: "HTTP/1.1",
status: res.statusText,
akto_account_id: "1000000",
akto_vxlan_id: "0",
is_pending: "false",
source: "MIRRORING",
tag: "{\n \"service\": \"cloudflare\"\n}"
};
return JSON.stringify({ batchData: [value] });
}
Step 3: Configure Worker Routing with Service Binding
To securely connect your client Worker (e.g., mcp worker) with the Akto Mini-Runtime-Service Container, use service binding. This allows your Worker to call the container Worker internally, without exposing it to the public internet.
1. Add Service Binding to Your Client Worker
In the Cloudflare Dashboard, go to Workers & Pages.
Under Overview, select your client Worker (e.g., mcp worker).
Navigate to Settings > Bindings.
Click Add binding and select Service binding.
In the Variable name field, enter:
data_injection_worker
In Service binding, select the container Worker you created in Step 1.
In Entrypoint, select the container's Durable Object name.
Click Add and then Deploy your Worker.
2. Restrict Container Worker to Internal Network
Go to the container Worker in the Cloudflare Dashboard.
Click Settings.
Navigate to Domains & Routes.
Disable both workers.dev and Preview URLs.
This ensures your container Worker is only accessible internally via service binding, improving security.
Now, your client Worker can securely communicate with the Akto Mini-Runtime-Service Container using the data_injection_worker
binding, and your container Worker is not exposed
Step 4: Verify the Setup
Confirm that API traffic data (requests and responses) are captured on the Akto dashboard under the respective api collection.
Check logs of your Cloudflare container for any initialization messages from the extension.
Go back to the Akto Dashboard.
Navigate to Api Collections > Hostname.
You should start seeing the traffic from your Cloudflare Worker.
Get Support for your Akto setup
There are multiple ways to request support from Akto. We are 24X7 available on the following:
In-app
intercom
support. Message us with your query on intercom in Akto dashboard and someone will reply.Join our discord channel for community support.
Contact
[email protected]
for email support.Contact us here.
Last updated
Was this helpful?