Network Troubleshooting Notes for Running Antigravity CLI in Docker
Network Troubleshooting Notes for Running Antigravity CLI in Docker
1. Background
The original Docker-based development environment was mainly intended for using Codex.
That initial setup already included a Squid container.
app-editor
Container for Codex and general development work
squid
Proxy container inside the Docker network
git-bare
Internal Git server on the restricted network
app-editor was attached to multiple Docker networks.
services:
app-editor:
networks:
- restricted
- ingress
- outbound
git-bare:
networks:
restricted:
aliases:
- git
The internal Git server was available on the restricted network and was referenced from app-editor like this:
git://git:9418/app.git
At this stage, no Antigravity CLI-specific networking requirements had surfaced yet.
2. Architecture: Before and After
2.1 Before: Initial Architecture
flowchart LR
subgraph Docker["Docker Compose"]
subgraph Restricted["restricted network"]
App["app-editor<br/>Codex / development work / agy added later"]
Git["git-bare<br/>git://git:9418/app.git"]
end
subgraph ProxyNet["proxy / outbound network"]
SquidC["Squid container<br/>http://squid:3128"]
end
App -->|internal git| Git
App -.->|HTTP_PROXY / HTTPS_PROXY| SquidC
SquidC --> Internet["Internet"]
end
App -.->|Some Antigravity CLI traffic bypassed the proxy| Internet
classDef problem fill:#ffe6e6,stroke:#cc0000,color:#000;
class Internet,App problem;
The initial environment was a Docker development setup designed around Codex, and it already included a Squid container.
After adding Antigravity CLI, some traffic appeared to bypass Squid and attempt direct outbound connections. This exposed DNS, proxy, and routing issues.
2.2 After: Final Architecture
flowchart LR
subgraph Docker["Docker Compose"]
subgraph Internal["internal / private networks"]
App2["app-editor<br/>Codex / Antigravity / npm / Go"]
Git2["git-bare<br/>git://git:9418/app.git"]
end
end
App2 -->|private IP destinations are allowed| Git2
App2 --> Bridge["Docker bridge<br/>docker0 / br-*"]
Bridge --> DU["DOCKER-USER"]
DU --> Filter["DOCKER-EGRESS-FILTER"]
Filter --> Private["private IP ranges<br/>10/8, 172.16/12, 192.168/16<br/>RETURN"]
Filter --> IPSet["ipset docker_allowed_v4<br/>allowed IPs generated from DNS"]
Filter --> Drop["LOG + DROP<br/>unauthorized traffic"]
IPSet -->|TCP 80/443 only| Internet2["Internet"]
Drop -.-> Blocked["Unauthorized destinations<br/>such as example.com"]
DockerNAT["Docker NAT / MASQUERADE<br/>left to Docker"] -.-> Bridge
classDef ok fill:#e6ffed,stroke:#008000,color:#000;
classDef block fill:#ffe6e6,stroke:#cc0000,color:#000;
classDef control fill:#e6f0ff,stroke:#005fcc,color:#000;
class Private,IPSet,Internet2 ok;
class Drop,Blocked block;
class DU,Filter,DockerNAT control;
In the final setup, Squid, transparent proxying, and standalone nftables-based filtering were removed from the core design.
Outbound traffic is controlled using Docker’s DOCKER-USER chain and ipset.
Docker continues to manage NAT and MASQUERADE, while custom rules only allow TCP 80/443 to approved IPs and allow private IP destinations for internal Docker communication.
3. Problem
After adding Antigravity CLI (agy), outbound networking issues appeared.
A typical error looked like this:
Eligibility check failed:
Post "https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist":
dial tcp: lookup daily-cloudcode-pa.googleapis.com on 127.0.0.11:53:
server misbehaving
In other cases, the error looked like this:
dial tcp 142.251.24.95:443: connect: network is unreachable
In short, Antigravity CLI introduced networking requirements that had not been a problem when the environment was only used with Codex.
The affected areas included:
Docker DNS
HTTPS traffic to external APIs
proxy configuration
Docker network default routes
whether Antigravity CLI respects proxy settings
4. Attempt 1: Use the Existing Squid Container
4.1 Goal
The first idea was to use the existing Squid container and route Antigravity CLI’s outbound traffic through it.
4.2 Attempted Setup
From app-editor, the Squid container inside the Docker network was referenced as:
http://squid:3128
The following environment variables were configured:
HTTP_PROXY=http://squid:3128
HTTPS_PROXY=http://squid:3128
http_proxy=http://squid:3128
https_proxy=http://squid:3128
Basic TCP connectivity and HTTP CONNECT through Squid were verified from Python.
CONNECT requests to the following destinations were confirmed to work:
oauth2.googleapis.com:443
daily-cloudcode-pa.googleapis.com:443
4.3 Result
The Squid container itself was working.
However, Antigravity CLI appeared to bypass the proxy for some of its traffic.
As a result, simply relying on HTTP_PROXY and HTTPS_PROXY did not solve the issue.
4.4 Lesson Learned
Being able to reach the Squid container
does not mean that Antigravity CLI will always use it.
5. Attempt 2: Fix Squid Hostname and IP Resolution
5.1 Goal
The goal was to avoid errors such as:
proxyconnect tcp: lookup squid ... no such host
5.2 Attempt
Because name resolution for squid looked unstable in some cases, the Squid container’s IP was fixed in Docker Compose, and proxy settings were also tested using the IP address directly.
Example:
HTTPS_PROXY=http://172.30.0.52:3128
5.3 Result
This helped clarify name resolution and reachability issues for the Squid container.
However, it did not fix the direct outbound connections made by Antigravity CLI during the eligibility check.
5.4 Lesson Learned
Fixing Squid hostname resolution does not help
if the application bypasses the proxy.
6. Attempt 3: Test CoreDNS
6.1 Positioning
CoreDNS was not part of the initial architecture.
It was only tested as part of investigating DNS control for Claude-related URLs and other external service names.
This was an experiment, not a committed part of the architecture.
6.2 Goal
The idea was to check whether pointing a specific domain to a chosen IP could control the traffic.
Example:
daily-cloudcode-pa.googleapis.com -> Squid IP
6.3 Result
This did not solve the problem.
Even if DNS points the domain to the Squid IP, the HTTPS client still connects to:
Squid-IP:443
But Squid listens as an HTTP proxy on port 3128.
Squid:
listens on 3128 as an HTTP proxy
Antigravity CLI:
tries to connect to 443 as an HTTPS client
DNS rewriting does not magically convert 443 traffic into proxy traffic on 3128.
6.4 Lesson Learned
DNS can change the destination IP.
It cannot change the destination port or protocol into an HTTP proxy connection.
7. Attempt 4: iptables + redsocks Inside the Container
7.1 Goal
If Antigravity CLI ignored proxy settings, the next idea was to forcibly redirect TCP 80/443 traffic to a proxy from inside the container.
7.2 Attempted Setup
app-editor
↓ iptables OUTPUT
redsocks
↓
Squid container
7.3 Result
When app-editor had no default route, connections to external IPs failed at the routing stage.
network is unreachable
In that situation, the packet never reached the NAT OUTPUT rule.
The kernel rejected the connection before redsocks had a chance to intercept it.
7.4 Lesson Learned
To intercept traffic with iptables OUTPUT,
the destination must first be routable.
With only an internal network and no default route,
the connection fails before NAT OUTPUT can redirect it.
8. Attempt 5: Host-Level Squid + Transparent Proxy
8.1 Goal
The next idea was to avoid doing traffic interception inside the container and instead redirect Docker bridge traffic on the host.
8.2 Attempted Setup
At this point, Squid was also installed on the host.
Docker container
↓
Docker bridge
↓ DNAT
redsocks / sing-box
↓
host Squid
↓
Internet
Because redsocks looked old and lightly maintained, sing-box was also tested.
8.3 Result
Redirecting TCP 80/443 from Docker bridge traffic to host-side Squid worked.
However, this exposed a new problem.
Squid’s access log showed entries like this:
CONNECT 172.66.147.243:443
CONNECT 104.20.23.154:443
The expected logs would have looked like this:
CONNECT daily-cloudcode-pa.googleapis.com:443
CONNECT api.openai.com:443
8.4 Cause
With transparent proxying, the application first resolves the domain to an IP address and then attempts to connect to that IP.
The transparent proxy intercepts the TCP connection after that has already happened.
Application
↓ DNS resolution
domain -> IP
↓
connects to IP:443
↓
transparent proxy intercepts the TCP connection
↓
Squid sees IP:443
8.5 Lesson Learned
Explicit proxy:
Squid sees the domain name.
dstdomain ACL works.
Transparent proxy:
Squid only sees IP:port.
dstdomain ACL does not work as expected.
9. Attempt 6: IP Allowlist Control with Squid
9.1 Goal
Since domains were no longer visible through the transparent proxy, the next idea was to allow or deny traffic based on IP addresses generated from DNS resolution.
9.2 Attempted Setup
allowed domains
↓ DNS resolution
allowed IPs
↓
Squid dst ACL
Instead of Squid’s dstdomain ACL, the dst ACL was considered.
9.3 Result
IP-based allow/deny control looked feasible.
However, this raised a design question:
If traffic is controlled only by IP,
why keep Squid in the path at all?
9.4 Lesson Learned
Squid is most useful when it can see the CONNECT hostname.
If transparent proxying only gives Squid an IP address,
Squid becomes little more than an IP filter.
10. Attempt 7: IP Allowlist Control with nftables
10.1 Goal
The next idea was to remove Squid and sing-box entirely, and directly filter Docker outbound traffic using nftables.
10.2 Attempted Setup
Docker container
↓
Docker bridge
↓
nftables forward hook
↓
allow only TCP 80/443 to approved IPs
10.3 Result
Unauthorized destinations such as example.com were successfully dropped.
However, allowed destinations such as api.openai.com still timed out, even though they were not being dropped.
The situation was:
example.com:
drop log exists
connection fails
api.openai.com:
no drop log
IP exists in allowed_v4
but connection times out
The cause was that Docker NAT / MASQUERADE was not working correctly in this setup.
Adding the following rule manually fixed the connection:
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 -j MASQUERADE
10.4 Lesson Learned
Accepting traffic in nftables filter rules is not enough.
Without Docker source NAT, return traffic cannot reach the container.
Mixing Docker-managed iptables/NAT rules
with custom nftables rules can easily lead to subtle breakage.
11. Final Approach: DOCKER-USER + ipset
11.1 Policy
Docker should continue to manage NAT and MASQUERADE.
Custom outbound filtering should be placed in Docker’s DOCKER-USER chain.
11.2 Final Architecture
Docker container
↓
Docker bridge
↓
DOCKER-USER
↓
DOCKER-EGRESS-FILTER
↓
ipset docker_allowed_v4
11.3 Filtering Logic
private IP destinations
→ RETURN
allowed IP + TCP 80/443
→ RETURN
UDP 443
→ DROP
everything else
→ LOG + DROP
11.4 Result
This avoided fighting Docker’s NAT behavior while still allowing outbound filtering.
Unauthorized destinations such as example.com are dropped.
Allowed destinations such as api.openai.com are allowed if their resolved IPs exist in the allowlist.
12. strict / relaxed Modes
Two allowlist modes were prepared for operational convenience.
12.1 strict
This mode allows a relatively minimal set of domains for Antigravity, OpenAI, Anthropic, and Google APIs.
api.openai.com
auth.openai.com
chatgpt.com
ab.chatgpt.com
platform.openai.com
oauth2.googleapis.com
antigravity-unleash.goog
www.googleapis.com
playwright.azureedge.net
antigravity-cli-auto-updater-974169037036.us-central1.run.app
storage.googleapis.com
api.anthropic.com
platform.claude.com
daily-cloudcode-pa.googleapis.com
12.2 relaxed
This mode adds development-related domains such as npm, GitHub, Go modules, and Playwright.
api.openai.com
auth.openai.com
chatgpt.com
ab.chatgpt.com
platform.openai.com
oauth2.googleapis.com
antigravity-unleash.goog
www.googleapis.com
playwright.azureedge.net
api.github.com
github.com
codeload.github.com
objects.githubusercontent.com
raw.githubusercontent.com
proxy.golang.org
sum.golang.org
storage.googleapis.com
registry.npmjs.org
npmjs.com
www.npmjs.com
cdn.playwright.dev
api.anthropic.com
platform.claude.com
The mode can be switched using:
docker-egress-strict
docker-egress-relaxed
13. Handling Internal Docker Traffic
The Compose setup also included an internal Git server.
app-editor accessed it as:
git://git:9418/app.git
If outbound filtering is applied broadly to Docker bridge traffic, internal Docker traffic can accidentally be affected.
To avoid that, private IP destinations were excluded from filtering.
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
This allows internal services such as the Git server to keep working while still filtering external traffic.
14. Reapplying Rules After Docker Restart
iptables and ipset rules are not persistent by themselves.
Also, Docker may reconstruct its iptables rules when the Docker daemon restarts.
To reapply the custom rules after Docker starts, a systemd drop-in was added:
[Service]
ExecStartPost=/usr/local/sbin/update-docker-egress-ipset.sh
This reapplies the DOCKER-USER + ipset rules after Docker daemon startup or restart.
15. Final Compromise
In the end, strict FQDN-based control was abandoned.
The reasons were:
Antigravity CLI does not appear to fully respect proxy settings.
Transparent proxying does not preserve domain names for Squid.
SNI-based filtering adds implementation complexity and can be spoofed.
Direct nftables filtering can interfere with Docker NAT.
The final compromise was:
Use DNS-resolution-based IP allowlists.
Let Docker manage NAT.
Use DOCKER-USER + ipset for outbound filtering.
Switch between strict and relaxed modes depending on the task.
This is not strict domain-level filtering.
If a CDN or shared IP hosts multiple domains, allowing that IP may also allow access to other domains on the same IP.
However, for this use case, it was a practical compromise.
16. Final Architecture Summary
app-editor container
↓
Docker bridge
↓
DOCKER-USER
↓
DOCKER-EGRESS-FILTER
↓
ipset docker_allowed_v4
├─ private IP → RETURN
├─ allowed IP + TCP 80/443 → RETURN
├─ UDP 443 → DROP
└─ others → LOG + DROP
Docker NAT / MASQUERADE is left under Docker’s control.
Docker NAT:
managed by Docker
Egress filtering:
managed by DOCKER-USER + ipset
17. Conclusion
The original environment was a Docker development setup designed around Codex.
It already included a Squid container, but Antigravity CLI-specific networking issues had not yet appeared.
After adding Antigravity CLI, problems surfaced around outbound HTTPS traffic, DNS, proxy behavior, and Docker network routing.
The following approaches were tested in sequence:
existing Squid container
Squid hostname/IP fixes
CoreDNS experiment
iptables + redsocks inside the container
host-side Squid + transparent proxy
Squid IP allowlist
nftables IP allowlist
In the end, both Squid and transparent proxying were removed from the final design.
The final solution was DOCKER-USER + ipset, which works more naturally with Docker.
This is not strict FQDN-based filtering. It is DNS-resolution-based IP filtering.
However, it is a practical solution for restricting outbound traffic from Docker containers, even for tools such as Antigravity CLI that may bypass proxy settings.