Running codex cli in VS Code devcontainer
What is the type of issue?
Documentation is missing
Edit: I finally got it to work, see the end of the issue. Documentation is still missing, however ;)
What is the issue?
Environment
I'm running Claude Code in a VS Code devcontainer with a custom Dockerfile. I wanted to try the Codex CLI in the same project. Codex works fine if I run it on the host.
OS: Windows 11 with WSL2
Setup steps
- added
RUN npm i -g @openai/codexto my Dockerfile - logged in locally
- mounted auth.json into the container using "source=${localEnv:HOME}/.codex/auth.json,target=/etc/codex/auth.json,readonly,type=bind"
- added
cp /etc/codex/auth.json /home/node/.codex/auth.jsonto the postCreateCommand - added api.openai.com to the URL whitelist
- run codex inside container, verify /status prints as expected
- curl https://api.openai.com returns as expected
- disabling the firewall temporarily, codex runs normally
Issue
codex is "thinking" forever when I try any prompt. Pressing ctrl+c after 15 minutes and checking /status afterwards displays token usage as 0. This seems to be a networking issue, but there is no log or error information available
If I understand the problem correctly, codex is trying to access IP addresses not listed when using dig +short A api.openai.com, either because it's trying to access another domain or because api.openai.com changes IPs (some cloudflare thing?)
Resolving api.openai.com...
Adding 162.159.140.245 for api.openai.com
Adding 172.66.0.243 for api.openai.com
This is the output of netstat -np --inet | grep "codex" tcp 0 1 172.17.0.2:38806 172.64.155.209:443 SYN_SENT 1089/codex-x86_64-u
latertcp 0 1 172.17.0.2:47382 104.18.32.47:443 SYN_SENT 1089/codex-x86_64-u
I don't understand why this is not a problem when using run_in_container.sh where you use the same dig command I use in init_firewall.sh
_Please provide documentation in README.md about which URLs have to be whitelisted and how you recommend to run codex inside a container_
Additional information
Status output
• Path: /workspace
• Approval Mode: on-request
• Sandbox: workspace-write
• Session ID: 841645eb-c10b-4dff-85b7-bdc55a021763
👤 Account
• Signed in with ChatGPT
• Login:
• Plan: Plus
🧠 Model
• Name: gpt-5
• Provider: OpenAI
• Reasoning Effort: Medium
• Reasoning Summaries: Auto```
curl https://api.openai.com
{
"message": "Welcome to the OpenAI API! Documentation is available at https://platform.openai.com/docs/api-reference"
}%
̀ ``
excerpt from
# Resolve and add other allowed domains
for domain in \
"api.openai.com" \
"googlehosted.l.googleusercontent.com" \
"registry.npmjs.org" \
"api.anthropic.com" \
"sentry.io" \
"statsig.anthropic.com" \
"statsig.com"; do
echo "Resolving $domain..."
ips=$(dig +short A "$domain")
if [ -z "$ips" ]; then
echo "ERROR: Failed to resolve $domain"
exit 1
fi
while read -r ip; do
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
echo "ERROR: Invalid IP from DNS for $domain: $ip"
exit 1
fi
echo "Adding $ip for $domain"
ipset add allowed-domains "$ip"
done < <(echo "$ips")
done
Edit:
I figured it out. After having GPT-5 rewrite the script to add cloudflare IPs to the whitelist, codex works in the container now. Here's my full init-firewall.sh for reference
#!/bin/bash
set -euo pipefail # Exit on error, undefined vars, and pipeline failures
IFS=$'\n\t' # Stricter word splitting
# 1. Extract Docker DNS info BEFORE any flushing
DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true)
# Flush existing rules and delete existing ipsets
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
ipset destroy allowed-domains 2>/dev/null || true
# 2. Selectively restore ONLY internal Docker DNS resolution
if [ -n "$DOCKER_DNS_RULES" ]; then
echo "Restoring Docker DNS rules..."
iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat
else
echo "No Docker DNS rules to restore"
fi
# First allow DNS and localhost before any restrictions
# Allow outbound DNS
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
# Allow inbound DNS responses
iptables -A INPUT -p udp --sport 53 -j ACCEPT
# Allow outbound SSH
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
# Allow inbound SSH responses
iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
# Allow localhost
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Create ipset with CIDR support (IPv4)
ipset create allowed-domains hash:net
# --- GitHub IPv4 ranges ---
echo "Fetching GitHub IP ranges..."
gh_json=$(curl -s https://api.github.com/meta)
if [ -z "$gh_json" ]; then
echo "ERROR: Failed to fetch GitHub IP ranges"
exit 1
fi
if ! echo "$gh_json" | jq -e '.web and .api and .git' >/dev/null; then
echo "ERROR: GitHub API response missing required fields"
exit 1
fi
echo "Processing GitHub IPs..."
echo "$gh_json" \
| jq -r '(.web + .api + .git)[]' \
| aggregate -q \
| while read -r cidr; do
if [[ ! "$cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; then
echo "ERROR: Invalid CIDR range from GitHub meta: $cidr"
exit 1
fi
echo "Adding GitHub range $cidr"
ipset add -! allowed-domains "$cidr"
done
# --- Cloudflare IPv4 ranges ---
echo "Fetching Cloudflare IP ranges..."
cf_json=$(curl -s https://api.cloudflare.com/client/v4/ips)
if [ -z "$cf_json" ]; then
echo "ERROR: Failed to fetch Cloudflare IP ranges"
exit 1
fi
if ! echo "$cf_json" | jq -e '.result.ipv4_cidrs' >/dev/null; then
echo "ERROR: Cloudflare API response missing ipv4_cidrs"
exit 1
fi
echo "Processing Cloudflare IPv4 CIDRs..."
echo "$cf_json" \
| jq -r '.result.ipv4_cidrs[]' \
| aggregate -q \
| while read -r cidr; do
if [[ ! "$cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; then
echo "ERROR: Invalid CIDR range from Cloudflare: $cidr"
exit 1
fi
echo "Adding Cloudflare range $cidr"
ipset add -! allowed-domains "$cidr" # -! ignores duplicates
done
# Note: IPv6 ranges are available from Cloudflare but not added here since this script configures iptables (IPv4).
# Add a separate inet6 ipset + ip6tables if you need IPv6.
# Resolve and add other allowed domains (A records only)
for domain in \
"api.openai.com" \
"platform.openai.com" \
"googlehosted.l.googleusercontent.com" \
"registry.npmjs.org" \
"api.anthropic.com" \
"sentry.io" \
"statsig.anthropic.com" \
"statsig.com"
do
echo "Resolving $domain..."
ips=$(dig +short A "$domain" | grep -E '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' | sort -u)
if [ -z "$ips" ]; then
echo "ERROR: Failed to resolve $domain"
exit 1
fi
while read -r ip; do
echo "Adding $ip for $domain"
ipset add -! allowed-domains "$ip"
done <<< "$ips"
done
# Get host IP from default route
HOST_IP=$(ip route | awk '/^default/ {print $3; exit}')
if [ -z "$HOST_IP" ]; then
echo "ERROR: Failed to detect host IP"
exit 1
fi
HOST_NETWORK=$(echo "$HOST_IP" | sed 's/\.[0-9]*$/.0\/24/')
echo "Host network detected as: $HOST_NETWORK"
# Set up remaining iptables rules
iptables -A INPUT -s "$HOST_NETWORK" -j ACCEPT
iptables -A OUTPUT -d "$HOST_NETWORK" -j ACCEPT
# Set default policies to DROP first
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP
# First allow established connections for already approved traffic
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Then allow only specific outbound traffic to allowed domains
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT
echo "Firewall configuration complete"
echo "Verifying firewall rules..."
if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed - was able to reach https://example.com"
exit 1
else
echo "Firewall verification passed - unable to reach https://example.com as expected"
fi
# Verify GitHub API access
if ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed - unable to reach https://api.github.com"
exit 1
else
echo "Firewall verification passed - able to reach https://api.github.com as expected"
fi
# Verify Anthropic API access
if ! curl --connect-timeout 5 https://api.anthropic.com >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed - unable to reach https://api.anthropic.com"
exit 1
else
echo "Firewall verification passed - able to reach https://api.anthropic.com as expected"
fi
# Verify OpenAI API access
if ! curl --connect-timeout 5 https://api.openai.com >/dev/null 2>&1; then
echo "ERROR: Firewall verification failed - unable to reach https://api.openai.com/"
exit 1
else
echo "Firewall verification passed - able to reach https://api.openai.com/ as expected"
fi
# Cloudflare reachability probe
if ! curl --connect-timeout 5 https://www.cloudflare.com/cdn-cgi/trace >/dev/null 2>&1; then
echo "WARN: Could not reach Cloudflare trace endpoint; check ranges and DNS."
else
echo "Cloudflare verification passed."
fiThis issue has 3 comments on GitHub. Read the full discussion on GitHub ↗