Docker Development Guide
November 24, 2025 · View on GitHub
This guide covers Docker-related development workflows, including building images, configuring proxies for users in China, and troubleshooting common issues.
Table of Contents
- Building Docker Images Locally
- Proxy Configuration for China Users
- Troubleshooting
- Docker Image Architecture
Building Docker Images Locally
Quick Start
Option 1: Automated Build Script (Recommended)
# From project root directory
./scripts/build-docker.sh
# Force rebuild without cache
./scripts/build-docker.sh --no-cache
This script automatically:
- Detects proxy from environment or
~/.docker/config.json - Tests if proxy is working
- Pulls base image with proxy
- Builds with appropriate flags
Option 2: Quick Verification (Fast Testing)
If you're making Dockerfile changes and want to verify them quickly without rebuilding everything:
# Verify the build process without full rebuild
./scripts/verify-docker-build.sh
# Test specific stage only
./scripts/verify-docker-build.sh --stage builder
./scripts/verify-docker-build.sh --stage production
# Keep test images for debugging
./scripts/verify-docker-build.sh --no-cleanup
This verification script:
- Uses cached layers from previous builds
- Tests build stages independently
- Verifies artifacts are created correctly
- Much faster than full rebuild (2-3 minutes vs 10 minutes)
Option 3: Manual Build
# From project root directory
docker build -t idea-forge:latest .
Building with Build Arguments
If you need to pass build-time arguments (e.g., for proxy configuration inside the container):
docker build \
--build-arg HTTP_PROXY=http://host.docker.internal:7897 \
--build-arg HTTPS_PROXY=http://host.docker.internal:7897 \
--build-arg NO_PROXY=localhost,127.0.0.1 \
-t idea-forge:latest .
Note: host.docker.internal is a special DNS name that resolves to your host machine's IP from inside the Docker container.
Build Time
- First build: 5-10 minutes (depends on your network and system)
- Subsequent builds: Faster due to Docker layer caching
Proxy Configuration for China Users
If you're in China or behind a corporate firewall, Docker might have trouble pulling images from Docker Hub or downloading packages during build. Here's how to configure proxy support.
Understanding Docker Proxy Layers
Why do we need to configure proxy in multiple places?
Docker has 3 separate network contexts that each need their own proxy configuration:
| Layer | What Needs Proxy | Config Location | Why Needed |
|---|---|---|---|
| Docker Daemon | Pulling images from Docker Hub | Docker Desktop UI (Mac/Windows)/etc/docker/daemon.json (Linux) | Downloads base images like node:20.18.1-alpine |
| Docker CLI | Build metadata checks | ~/.docker/config.json | Fetches image metadata and layer info |
| Inside Container | Alpine apk, npm registry | --build-arg HTTP_PROXY=... | Downloads packages during build |
Important: These are separate programs with separate network stacks, so they need separate proxy configs.
Quick Answer: Use the Build Script
The simplest solution:
# Configure proxy once in ~/.docker/config.json
# Then just run:
./scripts/build-docker.sh
The script handles all proxy detection and configuration automatically.
Prerequisites
- A working proxy tool (Clash, V2Ray, etc.)
- Know your local proxy port (e.g., 7897)
Working Configuration (Tested)
This configuration has been verified to work:
Step 1: Configure Clash
-
Enable "Allow LAN" (允许局域网连接)
- Open Clash settings
- Enable "Allow LAN" option
- This allows Docker to connect to
127.0.0.1:7897
-
Set Proxy Mode to "Global"
- Click the "Global" tab in Clash interface
- This ensures ALL traffic (including Docker Hub) uses the proxy
- Alternative: Use "Rule" mode with custom rules (see below)
Step 2: Configure Docker Desktop
Option A: Use System Proxy (Recommended)
- Open Docker Desktop → Settings → Resources → Proxies
- Select "System proxy"
- Click "Apply & Restart"
This automatically uses your system's proxy settings (managed by Clash).
Option B: Manual Proxy Configuration
- Open Docker Desktop → Settings → Resources → Proxies
- Select "Manual proxy configuration"
- Set:
- Web Server (HTTP):
http://127.0.0.1:7897 - Secure Web Server (HTTPS):
http://127.0.0.1:7897 - Bypass for these hosts:
localhost,127.0.0.1
- Web Server (HTTP):
- Click "Apply & Restart"
Step 3: Configure Docker Daemon (~/.docker/config.json)
Edit ~/.docker/config.json to add proxy configuration:
{
"proxies": {
"default": {
"httpProxy": "http://127.0.0.1:7897",
"httpsProxy": "http://127.0.0.1:7897",
"noProxy": "localhost,127.0.0.1"
}
}
}
Important: After editing this file, restart Docker Desktop for changes to take effect.
Step 4: Build with Proxy
Important for Mac users: Docker Desktop on Mac has a bug where docker build doesn't always use proxy settings for pulling base images. Workaround: Pull the base image first!
# Step 1: Pull base image manually (this WILL use the proxy)
docker pull node:20.18.1-alpine
# Step 2: Build with --pull=false (use only cached images, don't check Docker Hub)
# Build args provide proxy for Alpine package manager (apk) inside the container
docker build \
--pull=false \
--build-arg HTTP_PROXY=http://host.docker.internal:7897 \
--build-arg HTTPS_PROXY=http://host.docker.internal:7897 \
-t idea-forge:latest .
Why this works:
docker pullcommand correctly uses the proxy (from config.json or Desktop UI)docker buildsometimes ignores proxy for image pulls (Docker Desktop bug)--pull=falsetells Docker to ONLY use locally cached images and skip metadata checks to Docker Hub--build-arg HTTP_PROXY/HTTPS_PROXYprovides proxy to commands INSIDE the container (Alpine apk, npm registry, etc.)- Once the image is cached locally, build proceeds with
--pull=false
What gets installed during build:
- Alpine packages:
openssl,openssl-dev,git,python3,make,g++(for native dependencies) - npm packages: 2500+ packages via pnpm (uses proxy for npm registry)
- Build time: 5-10 minutes on first build (downloads ~500MB)
Using Rule Mode Instead of Global
If you prefer to use Clash's "Rule" mode instead of "Global" mode, you need to add Docker Hub domains to your proxy rules.
Add Docker Hub Domains to Clash Rules
Edit your Clash configuration file (usually ~/.config/clash/config.yaml or similar) and add these domains to your proxy rules:
rules:
# Docker Hub domains
- DOMAIN-SUFFIX,docker.io,PROXY
- DOMAIN-SUFFIX,docker.com,PROXY
- DOMAIN,registry-1.docker.io,PROXY
- DOMAIN,auth.docker.io,PROXY
- DOMAIN,production.cloudflare.docker.com,PROXY
# Alpine Linux package repositories
- DOMAIN-SUFFIX,alpinelinux.org,PROXY
- DOMAIN,dl-cdn.alpinelinux.org,PROXY
# NPM registry (if needed)
- DOMAIN,registry.npmjs.org,PROXY
# Your other rules...
- MATCH,DIRECT
After editing:
- Reload Clash configuration
- Switch back to "Rule" mode
- Try building again
Summary: What You Need
✅ Clash Configuration:
- Allow LAN: Enabled
- Proxy Mode: Global (or Rule with Docker domains)
- Local Port: 7897 (or your configured port)
✅ Docker Desktop (Mac/Windows):
- Proxy: System proxy (recommended) or Manual with
http://127.0.0.1:7897
✅ Docker Config File (~/.docker/config.json):
- Add
proxies.defaultwith your proxy settings
✅ Build Command:
- Recommended:
./scripts/build-docker.sh(auto-detects everything) - Manual:
docker build --pull=false --build-arg HTTP_PROXY=... -t idea-forge:latest .
Linux Server with Network Restrictions
If you need to build on a Linux server (no Docker Desktop) with the same network restrictions:
Option 1: Use Pre-built Images (Recommended)
Skip all proxy configuration:
# Just pull the pre-built image from Docker Hub
docker pull chenxiaoyao6228/idea-forge:latest
This is why we have GitHub Actions - build once on GitHub, deploy anywhere!
Option 2: Build on Server with Proxy
1. Configure Docker Daemon (/etc/docker/daemon.json):
{
"proxies": {
"http-proxy": "http://127.0.0.1:7897",
"https-proxy": "http://127.0.0.1:7897",
"no-proxy": "localhost,127.0.0.1"
}
}
Restart Docker:
sudo systemctl restart docker
2. Configure Docker CLI (~/.docker/config.json):
{
"proxies": {
"default": {
"httpProxy": "http://127.0.0.1:7897",
"httpsProxy": "http://127.0.0.1:7897",
"noProxy": "localhost,127.0.0.1"
}
}
}
3. Build with Script:
# The build script works on Linux too!
./scripts/build-docker.sh
Or manually:
# Pull base image first
docker pull node:20.18.1-alpine
# Build (note: use docker0 bridge IP instead of host.docker.internal on Linux)
DOCKER_HOST_IP=$(ip -4 addr show docker0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
docker build \
--pull=false \
--build-arg HTTP_PROXY=http://$DOCKER_HOST_IP:7897 \
--build-arg HTTPS_PROXY=http://$DOCKER_HOST_IP:7897 \
-t idea-forge:latest .
Key Difference from Mac/Windows:
- Mac/Windows: Use
host.docker.internalto access host from container - Linux: Use docker0 bridge IP (usually
172.17.0.1) to access host from container
Troubleshooting
Testing if Your Proxy is Working
Before building, verify your proxy can reach Docker Hub:
# Test Docker Hub authentication server
curl --max-time 10 -x http://127.0.0.1:7897 'https://auth.docker.io/token?service=registry.docker.io'
# Expected output: A JSON token (long string starting with {"token":"eyJ...})
# Error output: Connection refused, timeout, or network error
What this tells you:
- ✅ Success (JSON token returned): Proxy is working, Docker should be able to pull images
- ❌ Connection refused: Clash is not running or "Allow LAN" is disabled
- ❌ Timeout: Proxy is unreachable or Clash is not connected to a VPN server
- ❌ 403 Forbidden: Proxy server is blocking the request
Issue: "Failed to resolve source metadata for docker.io/library/node"
Symptoms:
ERROR: failed to resolve source metadata for docker.io/library/node:20.18.1-alpine
Solutions:
-
Test if proxy is working (see above)
-
Check Docker Desktop proxy settings:
- Docker Desktop → Settings → Resources → Proxies
- Should be "System proxy" OR disabled (if using
~/.docker/config.json) - Click "Apply & Restart" after any changes
-
Verify
~/.docker/config.jsonhas proxy settings:cat ~/.docker/config.json | grep -A 5 proxies -
Restart Docker Desktop completely:
- Sometimes Docker Desktop doesn't pick up
config.jsonchanges until restarted - Quit Docker Desktop completely
- Wait 5 seconds
- Start Docker Desktop again
- Sometimes Docker Desktop doesn't pick up
-
Verify Clash is running and "Allow LAN" is enabled
Issue: "Error: exec: 'git': executable file not found"
Cause: Git is required for lefthook (git hooks) during dependency installation.
Solution: Already fixed in the Dockerfile by adding git to Alpine packages:
RUN apk add --no-cache openssl openssl-dev git python3 make g++
If you see this error, make sure you're using the latest Dockerfile.
Issue: "gyp ERR! find Python" or "Unable to detect compiler type"
Symptoms:
gyp ERR! find Python Python is not set from command line or npm configuration
Error: Unable to detect compiler type
Cause: Some native dependencies (ssh2, cpu-features, argon2) need Python and C++ build tools to compile.
Solution: Already fixed in the Dockerfile by adding build dependencies:
RUN apk add --no-cache openssl openssl-dev git python3 make g++
These packages provide the build environment needed for native node modules.
Issue: "could not connect to server (check repositories file)"
Symptoms:
WARNING: fetching https://dl-cdn.alpinelinux.org/alpine/...: could not connect to server
Cause: Alpine package manager can't reach CDN (network restriction).
Solution: Use build arguments to pass proxy into container:
docker build \
--build-arg HTTP_PROXY=http://host.docker.internal:7897 \
--build-arg HTTPS_PROXY=http://host.docker.internal:7897 \
-t idea-forge:latest .
Issue: "i/o timeout" or "DeadlineExceeded"
Symptoms:
ERROR: DeadlineExceeded: failed to fetch anonymous token: dial tcp ...:443: i/o timeout
Causes & Solutions:
-
Proxy not responding fast enough:
- Switch Clash to "Global" mode
- Check if Clash is actually connected to a server
- Try a different proxy server node
-
Docker not using proxy:
- Verify
~/.docker/config.jsonhas correct proxy settings - Restart Docker Desktop after config changes
- Check Docker Desktop proxy settings
- Verify
-
Firewall blocking:
- Temporarily disable firewall to test
- Add Docker to firewall exceptions
Issue: Registry Mirror Blocking (403 Forbidden)
Symptoms:
ERROR: unexpected status from HEAD request to https://9lkbm4yu.mirror.aliyuncs.com/...: 403 Forbidden
Cause: Docker is configured to use a Chinese mirror (Aliyun) that's blocking access.
Solution:
- Open Docker Desktop → Settings → Docker Engine
- Remove the
registry-mirrorssection:{ "registry-mirrors": [ "https://9lkbm4yu.mirror.aliyuncs.com" // Remove this ] } - Click "Apply & Restart"
This will make Docker pull directly from Docker Hub (via your proxy).
Docker Image Architecture
Multi-Stage Build
The Dockerfile uses a multi-stage build:
-
Builder Stage (
node:20.18.1-alpine AS builder)- Installs dependencies
- Builds contracts, API, and client
- Generates Prisma client
- Runs build-time scripts
-
Production Stage (
node:20.18.1-alpine AS production)- Copies only production dependencies
- Copies built artifacts from builder
- Installs PM2 for process management
- Sets up startup scripts
Image Size Optimization
- Uses Alpine Linux (minimal base image)
- Multi-stage build discards dev dependencies and build artifacts
- Only production
node_modulesare included - Total image size: ~800MB-1GB (mostly node_modules)
Runtime Configuration
The image is designed to be configured at runtime:
- ✅
.env.exampleis baked into the image (safe defaults) - ✅ Environment variables override defaults at runtime
- ❌ No secrets in the image
- ❌ No build-time secrets (except optional Sentry tokens)
See docs/development/EN/deployment.md for deployment configuration.
Advanced Topics
Customizing Build
Skip Certain Stages
# Build only up to builder stage (for testing)
docker build --target builder -t idea-forge:builder .
Use Different Base Image
Edit Dockerfile line 2:
FROM node:20.18.1-alpine AS builder
# Change to:
FROM node:20-alpine AS builder # Latest Node 20
Multi-Platform Builds
To build for different architectures:
# Build for linux/amd64 and linux/arm64
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t idea-forge:latest .
Build Cache
Docker caches layers to speed up builds. To force a fresh build:
# Clear cache and rebuild
docker build --no-cache -t idea-forge:latest .
Faster Development Iteration
When working on Dockerfile changes:
1. Initial build (slow - pulls everything):
./scripts/build-docker.sh
2. Make Dockerfile changes
3. Quick verification (fast - uses cached layers):
./scripts/verify-docker-build.sh --stage builder
4. If builder stage passes, test production:
./scripts/verify-docker-build.sh --stage production
This approach lets you iterate on Dockerfile changes much faster by leveraging Docker's layer cache. Only the changed layers need to be rebuilt.
Related Documentation
- Deployment Guide - Deploying built images
- Development Guide - General development setup
- Docker Compose - Local development services
Getting Help
If you encounter issues not covered here:
- Check Docker Desktop logs: Docker Desktop → Troubleshoot → View logs
- Check build output carefully for specific error messages
- Verify your proxy is actually working:
curl -x http://127.0.0.1:7897 https://www.google.com - Open an issue with full error logs on GitHub