Security & Hardening
CoderFlow runs AI agents inside Docker containers that hold credentials for your source code, your IBM i systems, and your AI providers. This page covers how to deploy it safely, how to verify your setup from the outside, and how to limit the damage if a container is ever compromised.
If you read only one section, read the first one. It describes a Docker behavior that has caused real-world incidents for administrators who reasonably believed their firewall protected them.
Docker Publishes Ports Around Your Firewall
When Docker publishes a container port, it inserts its own rules directly into the system's packet-forwarding chain. Those rules are evaluated before host firewalls such as ufw or firewalld ever see the traffic. A port published on all interfaces is reachable from the network even when your firewall says otherwise, and ufw status will not list it.
Never rely on a host firewall alone to protect a Docker host. Verify exposure from the outside, as shown below.
CoderFlow task containers publish ports for the in-browser editor and for application-server previews. These ports have no authentication of their own. All authentication happens in the CoderFlow server, which proxies browser traffic to them. CoderFlow binds all published container ports to 127.0.0.1, so they are reachable only by the CoderFlow server on the same host and never from the network.
Verify From the Outside
Configuration review is not verification. Test what the network can actually reach:
-
Port scan the host from outside its network (for example from a machine on a different network, or a cloud shell):
nmap -p- your-server.example.comOnly the ports you intentionally expose should appear, typically SSH and the CoderFlow server or reverse-proxy port. If unexpected high-numbered ports (32768 and up) appear, container ports are exposed.
-
Confirm authentication is enforced on everything that is reachable:
curl -i https://your-server.example.com/tasksThe response must be a
401or a redirect to the login page, never task data.
Repeat this check after Docker or CoderFlow upgrades and after firewall changes.
Recommended Network Topology
Run the CoderFlow server on a private network, not on the public internet. Remote users should reach it through your VPN. This removes the entire class of internet-borne attacks regardless of any single component's configuration.
Some integrations need inbound traffic from the internet (Git webhooks, Slack, Microsoft Teams, inbound automation webhooks). Do not expose the whole server for them:
- Use the dedicated webhook-only ingress listener for inbound automation webhooks, or the messaging-integrations listener for Slack and Teams. These serve only their integration endpoints on a separate port, so only that port needs to be reachable from the internet.
- For internet-exposed webhooks, configure a signature scheme so deliveries are cryptographically verified, and restrict source IPs at your firewall or reverse proxy where practical.
If you must expose the web UI itself to the internet, put it behind a TLS-terminating reverse proxy with trusted proxy headers enabled, and require MFA or SSO for all users.
Limit What a Compromised Container Can Reach
Task containers execute agent-driven code, including dependencies fetched from package registries. Treat them as semi-trusted: assume one could eventually run hostile code, and limit what that code can reach.
Task containers are not offline sandboxes. Depending on the environment and selected features, they need these outbound flows:
| Destination | Used for | Typical TCP port |
|---|---|---|
| The effective CoderFlow callback URL | Fetching Git credentials during repository sync and calling task-scoped CoderFlow APIs | The URL's configured port: normally 3000 for a direct server, or 443/the configured reverse-proxy port |
| A directly connected IBM i | Build, SSH, source sync, and file deploy | 22 |
| Profound Logic Remote Access Server (RAS) on a directly connected IBM i | SQL | 8240 |
| Profound UI on a directly connected IBM i | Interactive Sessions | The port in the connection's PUI Base URL |
| Internal Git or other configured connection hosts | Repository and environment-specific access | The port configured for that service, commonly 22 or 443 for Git |
| Internet services selected by the task | AI providers, Git hosts, package registries, MCP servers, and similar integrations | Service-specific, commonly 443 |
The callback URL is the value CoderFlow hands the container when it starts. Configure
site_url or CODERFLOW_INTERNAL_URL so that it names an address the container can resolve and reach. If you later change its protocol, host, or port, relaunch existing task containers because they keep the URL with which they were created.
Docker provides the DOCKER-USER firewall chain for controlling traffic that is forwarded through the host. Rules there are not overwritten by Docker. Docker normally creates a final RETURN rule in that chain; a rule added with iptables -A can land after it and never run. Insert your rules before that return instead. The example below uses explicit positions to keep the rules together and in the required order. Substitute the real addresses and keep only the ports the environment uses:
# Keep reply traffic for permitted outbound connections working. This must
# precede the private-range rejects because replies target the container's
# private bridge address.
sudo iptables -I DOCKER-USER 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# CoderFlow callback through a private reverse proxy
sudo iptables -I DOCKER-USER 2 -p tcp -d 192.0.2.10 --dport 443 -j ACCEPT
# Direct IBM i: SSH/build/sync/deploy and RAS/SQL
sudo iptables -I DOCKER-USER 3 -p tcp -d 192.0.2.20 --dport 22 -j ACCEPT
sudo iptables -I DOCKER-USER 4 -p tcp -d 192.0.2.20 --dport 8240 -j ACCEPT
# Internal HTTPS Git host
sudo iptables -I DOCKER-USER 5 -p tcp -d 192.0.2.30 --dport 443 -j ACCEPT
# Block the cloud metadata service (always safe; prevents credential
# theft from the hosting platform on AWS/Azure/GCP and similar)
sudo iptables -I DOCKER-USER 6 -d 169.254.169.254 -j REJECT
# Block the rest of your internal address space so a compromised
# container cannot scan or pivot into the LAN
sudo iptables -I DOCKER-USER 7 -d 10.0.0.0/8 -j REJECT
sudo iptables -I DOCKER-USER 8 -d 172.16.0.0/12 -j REJECT
sudo iptables -I DOCKER-USER 9 -d 192.168.0.0/16 -j REJECT
This example limits access to private networks; it is not a complete internet-egress allow list. If your policy ends the chain with a default reject or drop, first allow every external service the task requires. Provider, registry, Git, and MCP endpoints can change, so maintain and test that list as part of environment configuration.
The Callback May Need a Host INPUT Rule
DOCKER-USER does not govern a packet whose destination is an address owned by the Docker host itself; that packet is delivered through the host's INPUT path. A host firewall that accepts the CoderFlow port only from the LAN can still reject the callback because it sees the Docker bridge address as the source, commonly a 172.x address.
When the effective callback URL terminates on the Docker host, allow the task-container subnet to reach only that callback port in the host firewall. Determine the actual subnet instead of assuming the common default:
docker network inspect bridge --format '{{(index .IPAM.Config 0).Subnet}}'
Then add the equivalent rule using your firewall manager. For example, if the reported subnet is 172.17.0.0/16 and CoderFlow listens directly on port 3000:
sudo iptables -I INPUT -p tcp -s 172.17.0.0/16 --dport 3000 -j ACCEPT
If the callback terminates at a reverse proxy on the same host, allow its listen port instead. Do not open the port to all sources merely to make container callbacks work.
Troubleshooting Restricted Egress
These startup failures commonly indicate that the allow list or host firewall is too strict:
repository_syncfails, or the log says the Git credential helper failed to reach/api/git/credentials: the container cannot reach the effective CoderFlow callback URL. Check its DNS, protocol, certificate trust, port, theDOCKER-USERpath when routed through another address, and the hostINPUTpath when it terminates locally.- Connection preflight reports a timeout, no route to host, or connection refused: allow the configured destination and feature port. For a direct IBM i, check SSH on
22, RAS on8240, or the port from the PUI Base URL. - Per-task build-library creation fails after repository sync: the container needs SSH access to the configured IBM i on port
22.
After changing rules, launch a new task that exercises every configured connection. Existing containers retain their original callback URL. Also remember:
iptablesrules do not survive a reboot on their own. Persist them with your distribution's mechanism (for exampleiptables-persistenton Debian/Ubuntu).
Credentials Inside Task Containers
Connections marked Available For: Tasks or Deploy are injected into every task or deployment container in their environment, along with Git credentials and AI provider authentication. Any code running in such a container can read them. Plan accordingly:
- Use Automation-only connections for background jobs. They are read by server-side automations and never injected into any container. This matters most for connections with broad IBM i authority.
- Scope what you inject. Use IBM i profiles with the least authority the tasks actually need, and Git provider credentials limited to the repositories in the environment. Avoid injecting build or deploy credentials into interactive task environments that do not deploy.
- Restrict installed SSH keys at the destination. On systems reachable only from a known CoderFlow host, add an OpenSSH
from=source restriction to the installed key. CoderFlow does not add one automatically; follow the cautions in SSH Key Management. - Prefer separate environments over one broad one. Credentials are injected per environment, so an environment with fewer connections exposes less.
If You Suspect a Container Was Compromised
- Stop the container (Administration → Containers), but keep it for investigation. Do not delete it immediately.
- Rotate everything that was reachable from inside it:
- IBM i credentials (SSH keys and passwords) for the environment's connections
- Git provider credentials and tokens
- AI provider API keys and OAuth sessions
- CoderFlow user passwords and API keys, if the server itself may have been reached
- Check other containers and the host for unexpected processes and outbound connections (
docker statsanddocker tophelp; sustained maximum CPU in an idle task is a classic sign of a crypto-miner). - Re-verify network exposure from the outside, as described above, before returning to normal operation.
Host Sizing and Monitoring
CoderFlow enables CPU, memory, and process-count limits for runtime containers by default. Configure them under Server Settings → Containers:
- Leaving CPUs blank limits each container to half of the host's CPU cores, with a minimum of one CPU.
- Leaving Memory blank limits each container to half of the host's memory.
- Max Processes defaults to 512.
The limits apply to new task, interactive-session, deployment, test, documentation-publish, and prompt containers. Existing containers keep the limits with which they started, so recreate them after changing the setting. Disabling the limits lets one runaway workload compete with the server and every other container.
Resource limits do not replace host sizing or storage monitoring. Size the host per Server Requirements, watch the Administration → Health panel, and configure automatic Docker storage cleanup separately.
Keep the Server Updated
Security fixes ship as regular releases. Check Administration → Health → Check for Updates, or enable web-managed updates in Server Settings → Update Management to apply them from the browser. Review the release notes for security-relevant changes.