Setup and context — Eliminating "Works on My Machine" Forever
In AI-driven development, environment reproducibility directly impacts productivity. The code generated by Antigravity's agents depends heavily on the execution environment — Node.js versions, Python packages, system libraries, and more. The classic "works on my machine" problem between team members can devastate development velocity.
This article walks you through using Dev Containers and Docker to make your Antigravity AI development environment fully reproducible. From devcontainer.json design patterns to multi-stage builds and CI/CD integration, you'll build a production-ready setup step by step.
Target audience: Engineers familiar with Docker basics (docker build, docker run) who want to streamline team development with Antigravity
Prerequisites:
- Docker Desktop 4.x or later
- Antigravity IDE (latest version)
- Node.js 22 LTS / Python 3.12+ (inside the container)
What Are Dev Containers — IDE and Container Integration Architecture
Dev Containers is an open specification for defining and sharing development environments as containers. By placing a .devcontainer/devcontainer.json file at the root of your project, the IDE automatically starts a container and operates within it.
Traditional environment setup relied on README instructions like "install Node.js 22" or "Python 3.12 required," but actual version mismatches and missing system libraries caused constant issues. Dev Containers solve this problem at its root.
Antigravity natively supports Dev Containers. When it detects a .devcontainer/devcontainer.json, AI agents automatically operate within the container. This ensures that agent-generated code is always executed and tested in a unified environment.
Core Components of Dev Containers
A Dev Container setup consists of three main elements. First, devcontainer.json is the central configuration file that defines container settings, extensions, and port forwarding. Second, a Dockerfile (or docker-compose.yml) defines the base image, packages, and system configuration. Third, Features are reusable development tool packages that make it easy to add Node.js, Python, Docker-in-Docker, and more.
Basic Setup — Designing devcontainer.json for Antigravity
Let's start by designing a basic devcontainer.json optimized for Antigravity development.
// .devcontainer/devcontainer.json
{
"name": "Antigravity AI Dev",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
"NODE_VERSION": "22",
"PYTHON_VERSION": "3.12"
}
},
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"customizations": {
"antigravity": {
"extensions": [],
"settings": {
"terminal.integrated.defaultProfile.linux": "zsh"
}
}
},
"forwardPorts": [3000, 5432, 6379],
"postCreateCommand": "npm install && npm run setup",
"postStartCommand": "echo '✅ Dev Container ready'",
"remoteUser": "node"
}In this configuration, the build section uses a custom Dockerfile with Node.js and Python versions controlled through build arguments. The features section adds Git, GitHub CLI, and Docker-in-Docker, while forwardPorts automatically forwards development server and database ports.
Custom Dockerfile — Optimizing with Multi-Stage Builds
While basic Dev Containers can be built with Features alone, reproducing a production-like environment requires a custom Dockerfile. Multi-stage builds eliminate unnecessary layers, optimizing build time and image size.
# .devcontainer/Dockerfile
# ============================================
# Stage 1: Base image + system dependencies
# ============================================
ARG NODE_VERSION=22
FROM mcr.microsoft.com/devcontainers/javascript-node:${NODE_VERSION} AS base
ARG PYTHON_VERSION=3.12
RUN apt-get update && apt-get install -y --no-install-recommends \
python${PYTHON_VERSION} \
python${PYTHON_VERSION}-venv \
python3-pip \
build-essential \
libcairo2-dev \
libpango1.0-dev \
libjpeg-dev \
libgif-dev \
librsvg2-dev \
&& rm -rf /var/lib/apt/lists/*
# Create Python virtual environment
RUN python${PYTHON_VERSION} -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# ============================================
# Stage 2: Dependency installation
# ============================================
FROM base AS deps
WORKDIR /workspace
COPY package.json package-lock.json ./
RUN npm ci --prefer-offline
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# ============================================
# Stage 3: Development environment (final image)
# ============================================
FROM base AS development
# Copy node_modules from deps stage
COPY --from=deps /workspace/node_modules /workspace/node_modules
COPY --from=deps /opt/venv /opt/venv
# Set up zsh + oh-my-zsh
RUN apt-get update && apt-get install -y zsh \
&& sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended \
&& rm -rf /var/lib/apt/lists/*
# Developer convenience tools
RUN npm install -g \
typescript \
tsx \
@biomejs/biome \
turbo
WORKDIR /workspace
ENV NODE_ENV=developmentThere are three key points to this multi-stage build. Stage 1 locks system dependencies and uses --no-install-recommends to exclude unnecessary packages. Stage 2 copies only package.json and requirements.txt to maximize cache layer hits. Stage 3 copies only the artifacts from the deps stage and adds development tools.
With this setup, source code changes only trigger a rebuild of Stage 3, while dependency installation leverages cache, dramatically reducing build times after the initial run.
Secure Environment Variable and Secret Management
Development environments deal with API keys, database connection strings, and other secrets that must never be hardcoded in devcontainer.json or Dockerfiles.
// .devcontainer/devcontainer.json (excerpt)
{
"containerEnv": {
"NODE_ENV": "development",
"LOG_LEVEL": "debug",
"API_BASE_URL": "http://localhost:3000"
},
"secrets": {
"DATABASE_URL": {
"description": "PostgreSQL connection string",
"documentationUrl": "https://wiki.example.com/db-setup"
},
"STRIPE_SECRET_KEY": {
"description": "Stripe API secret key (test mode)"
}
}
}Non-sensitive environment variables go directly in containerEnv, while the secrets section prompts the IDE to ask users for secure input. This mechanism eliminates the risk of secrets being committed to the Git repository.
Another practical pattern is including a .env.example file in the repository and copying it to a local .env in the postCreateCommand.
{
"postCreateCommand": "cp -n .env.example .env && npm install"
}The -n flag prevents overwriting an existing .env, protecting individual settings.
Antigravity Agent Integration — Connecting AGENTS.md with Containers
Antigravity's agents automatically detect the container environment when running inside a Dev Container. Adding Dev Container-specific instructions to AGENTS.md optimizes agent behavior.
<!-- AGENTS.md -->
# Development Environment
This project runs in a Dev Container.
## Environment Constraints
- Node.js 22 LTS (pre-installed in container)
- Python 3.12 (installed in /opt/venv)
- PostgreSQL accessible on port 5432 (started via docker-compose)
- Redis accessible on port 6379
## Command Execution Rules
- Run `npm` commands in the /workspace directory
- Run Python scripts within the virtual environment (/opt/venv)
- Use `npm run db:migrate` for database migrations
- Use `npm run test` to run the full test suite
## Prohibited Actions
- Do not install npm packages globally (use node_modules only)
- Do not add system packages via apt (modify the Dockerfile instead)
- Do not edit .env directly (use .env.local)With this AGENTS.md, Antigravity's agents understand the container environment constraints and generate code accordingly. For example, when adding a Python package, the agent will suggest adding it to requirements.txt and rebuilding the Dockerfile, rather than running pip install directly.
Docker Compose for Multi-Service Architectures
Real-world projects need more than just an application server — databases, caches, and message queues are common requirements. Integrating docker-compose.yml with Dev Containers launches a complete development stack in one go.
# docker-compose.yml
services:
app:
build:
context: .
dockerfile: .devcontainer/Dockerfile
target: development
volumes:
- .:/workspace:cached
- node_modules:/workspace/node_modules
ports:
- "3000:3000"
- "9229:9229" # Node.js debug port
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
environment:
DATABASE_URL: postgres://dev:devpass@postgres:5432/appdb
REDIS_URL: redis://redis:6379
NODE_ENV: development
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: devpass
POSTGRES_DB: appdb
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/var/lib/redis/data
volumes:
node_modules:
postgres_data:
redis_data:Here's the devcontainer.json configuration for referencing Docker Compose:
// .devcontainer/devcontainer.json (Docker Compose version)
{
"name": "Antigravity Full Stack",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "app",
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"forwardPorts": [3000, 5432, 6379],
"postCreateCommand": "npm install && npm run db:migrate",
"remoteUser": "node"
}The key here is volume design. Making node_modules a named volume avoids filesystem differences between host and container (especially macOS file-watching overhead) and can dramatically speed up npm install.
Performance Optimization — Improving Build Times and I/O
The biggest factor in Dev Container developer experience is performance. Applying these optimization techniques can dramatically improve container startup times and file I/O performance.
Volume Mount Optimization
On macOS and Windows, file synchronization between host and container becomes a bottleneck. Use the cached mount option and separate heavy directories into named volumes.
{
"mounts": [
"source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
"source=antigravity-node-modules,target=/workspace/node_modules,type=volume",
"source=antigravity-next-cache,target=/workspace/.next,type=volume"
]
}Separating node_modules and .next (Next.js build cache) into volumes can improve file-watching performance by up to 10x on macOS.
Leveraging BuildKit Cache
Using BuildKit cache mounts in Dockerfiles allows reusing downloaded packages when re-running npm install or pip install.
# High-speed installation with BuildKit cache
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offline
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-cache-dir -r requirements.txtPrebuilds for Instant Startup
When using GitHub Codespaces or Antigravity Cloud, configuring prebuilds can reduce initial container startup time to near zero.
// .devcontainer/devcontainer.json
{
"updateContentCommand": "npm ci && npm run build",
"waitFor": "updateContentCommand"
}updateContentCommand runs during prebuild and is cached before postCreateCommand. This means dependency installation is skipped when creating new branches or rebuilding containers.
CI/CD Integration — Bridging Development and Production Environments
Using the same Dev Container definition in CI/CD pipelines eliminates the "passes in CI but fails locally" problem.
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Dev Container
uses: devcontainers/ci@v0.3
with:
runCmd: |
npm run lint
npm run typecheck
npm run test -- --coverage
npm run build
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/The devcontainers/ci action reads .devcontainer/devcontainer.json, builds the container, and runs commands inside it. This guarantees that local development, CI, and staging environments all share the same container definition.
GitHub Actions Cache Strategy
To reduce CI build times, leverage Docker layer caching.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ hashFiles('.devcontainer/**') }}
restore-keys: |
${{ runner.os }}-buildx-Team Development Best Practices
When running Dev Containers across a team, establishing these rules maintains environment consistency.
1. Mandatory PR Reviews for Dockerfile Changes
Set up a CODEOWNERS file to require reviews for Dev Container-related files.
# .github/CODEOWNERS
.devcontainer/ @team-lead @devops
Dockerfile @team-lead @devops
docker-compose.yml @team-lead @devops
2. Environment Diff Detection Script
Provide a script that verifies whether a team member's container environment is up to date.
#!/bin/bash
# scripts/verify-env.sh
# Container environment integrity check
echo "🔍 Starting environment verification..."
# Node.js version
NODE_EXPECTED="v22"
NODE_ACTUAL=$(node -v | cut -d. -f1)
if [ "$NODE_ACTUAL" != "$NODE_EXPECTED" ]; then
echo "❌ Node.js: expected $NODE_EXPECTED, got $NODE_ACTUAL"
exit 1
fi
# Python version
PYTHON_EXPECTED="3.12"
PYTHON_ACTUAL=$(python3 --version | cut -d' ' -f2 | cut -d. -f1,2)
if [ "$PYTHON_ACTUAL" != "$PYTHON_EXPECTED" ]; then
echo "❌ Python: expected $PYTHON_EXPECTED, got $PYTHON_ACTUAL"
exit 1
fi
# Required command checks
for cmd in git gh docker npm npx tsx biome; do
if ! command -v $cmd &> /dev/null; then
echo "❌ Missing command: $cmd"
exit 1
fi
done
echo "✅ Environment verification passed"3. Auto-Update Notifications
When the Dev Container definition changes, prompt team members to rebuild using postStartCommand.
{
"postStartCommand": "bash .devcontainer/check-rebuild.sh"
}#!/bin/bash
# .devcontainer/check-rebuild.sh
HASH_FILE="/tmp/.devcontainer-hash"
CURRENT_HASH=$(md5sum .devcontainer/Dockerfile .devcontainer/devcontainer.json 2>/dev/null | md5sum | cut -d' ' -f1)
if [ -f "$HASH_FILE" ]; then
STORED_HASH=$(cat "$HASH_FILE")
if [ "$CURRENT_HASH" != "$STORED_HASH" ]; then
echo ""
echo "⚠️ Dev Container definition has been updated."
echo " Rebuilding is recommended: Cmd+Shift+P → 'Rebuild Container'"
echo ""
fi
fi
echo "$CURRENT_HASH" > "$HASH_FILE"Troubleshooting — Common Issues and Solutions
Here are the most common problems you'll encounter when running Dev Containers, along with their solutions.
Slow Container Startup
The most common cause is volume mount I/O. Verify that consistency=cached is configured and that node_modules is separated into a named volume. If it's still slow, increase memory and CPU allocation in Docker Desktop settings.
Port Forwarding Not Working
Check whether the ports specified in forwardPorts are already in use by other processes. Use lsof -i :3000 to check port usage and change the port number if there's a conflict.
File Permission Issues
When host and container user IDs differ, file read/write operations throw permission errors. Set remoteUser correctly and use chown in the Dockerfile as needed.
# Set workspace ownership to the development user
RUN mkdir -p /workspace && chown -R node:node /workspaceSummary
By leveraging Dev Containers and Docker, you can make your Antigravity AI development environment fully reproducible, dramatically boosting your entire team's productivity. Combining devcontainer.json design, multi-stage build optimization, CI/CD integration, and team operation best practices eliminates one of development teams' biggest challenges: environment-related bugs.
Start with a basic devcontainer.json and gradually enrich the configuration as your project grows. Once built, onboarding new team members becomes as simple as "clone the repo and start the Dev Container."