A security-hardened, production-ready DevContainer base image featuring OpenCode CLI for AI-assisted development. Built on Node.js 25 with network isolation, essential development tools, and persistent configuration support.
This project provides a customizable base DevContainer image that integrates OpenCode, an open-source AI coding assistant, into a secure Linux development environment. Unlike using DevContainer features, this approach gives you explicit control over your environment configuration through Docker, making it easier to:
- Customize dependencies and tools
- Mount your own OpenCode settings to
/home/node/.opencode - Extend the base image with additional runtimes or services
- Control security policies and firewall rules
- Version your development environment
- OpenCode CLI - AI-assisted coding with support for multiple providers (Anthropic, OpenAI, OpenRouter)
- Network Isolation - Custom firewall with default-deny and explicit whitelist
- Node.js 25 - Latest LTS with optimized memory settings
- Development Tools - git, GitHub CLI, fzf, nano, vim, jq, and more
- Enhanced Shell - zsh with powerline10k theme and git-delta for better diffs
- VS Code Extensions - Pre-configured with OpenCode, ESLint, Prettier, and GitLens
- Persistent Storage - Command history and OpenCode configuration preserved across rebuilds
- Security Hardened - Non-root user, capability-based permissions, monitored outbound connections
Before you begin, ensure you have:
-
Docker Desktop or Docker CLI installed and running
-
Visual Studio Code with Dev Containers extension
-
OpenCode API Keys - At least one of:
- Anthropic API key (Get one here)
- OpenAI API key (Get one here)
- OpenRouter API key (Get one here)
-
Basic Understanding of DevContainers
mkdir my-opencode-project
cd my-opencode-projectCreate .devcontainer/devcontainer.json:
{
"name": "My OpenCode Project",
"image": "vegastyle/opencode-devcontainer-base:latest",
"capAdd": ["NET_ADMIN", "NET_RAW"],
"remoteUser": "node",
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
"customizations": {
"vscode": {
"extensions": [
"sst-dev.opencode"
]
}
}
}Create your OpenCode authentication file locally:
# Linux/macOS
mkdir -p ~/.local/share/opencode
nano ~/.local/share/opencode/auth.jsonAdd your API keys:
{
"anthropic": "your-anthropic-api-key",
"openai": "your-openai-api-key"
}Important: Never commit auth.json to version control!
- Open VS Code
- Press
F1orCtrl+Shift+P(Windows/Linux) /Cmd+Shift+P(macOS) - Type "Dev Containers: Reopen in Container"
- Wait for the container to build and start
Once inside the container, open the integrated terminal and run:
opencode --versionYou should see the OpenCode version number, confirming successful installation.
This is the fastest way to get started. The image is already built and hosted on Docker Hub.
Basic Configuration (.devcontainer/devcontainer.json):
{
"name": "My Project",
"image": "vegastyle/opencode-devcontainer-base:latest",
"capAdd": ["NET_ADMIN", "NET_RAW"],
"remoteUser": "node",
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
"mounts": [
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.local/share/opencode,target=/home/node/.local/share/opencode,type=bind,consistency=cached"
],
"customizations": {
"vscode": {
"extensions": [
"sst-dev.opencode",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"eamodio.gitlens"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"terminal.integrated.defaultProfile.linux": "zsh"
}
}
},
"containerEnv": {
"DEVCONTAINER": "true",
"NODE_OPTIONS": "--max-old-space-size=4096"
}
}If you want to customize the base image itself, you can build from the Dockerfile.
Step 1: Clone this repository
git clone https://github.com/vega-style/dev-containers.git
cd dev-containersStep 2: Create your devcontainer configuration (.devcontainer/devcontainer.json):
{
"name": "My Custom OpenCode",
"build": {
"dockerfile": "../path/to/opencode/.devcontainer/dockerfile",
"args": {
"OPENCODE_VERSION": "latest"
}
},
"capAdd": ["NET_ADMIN", "NET_RAW"],
"remoteUser": "node",
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh"
}Step 3: Customize the Dockerfile
You can modify opencode/.devcontainer/dockerfile to:
- Install additional tools
- Change the Node.js version
- Add language runtimes (Python, Go, Rust, etc.)
- Modify the OpenCode version
Step 4: Build and open
VS Code will automatically build the image when you reopen in the container.
OpenCode stores user settings in /home/node/.opencode. To customize:
Option 1: Mount your local settings
{
"mounts": [
"source=${localWorkspaceFolder}/.opencode,target=/home/node/.opencode,type=bind"
]
}Create .opencode/config.json in your project:
{
"defaultProvider": "anthropic",
"model": "claude-sonnet-4-5-20250929",
"maxTokens": 8000
}Option 2: Copy settings in a custom Dockerfile
FROM vegastyle/opencode-devcontainer-base:latest
COPY .opencode /home/node/.opencode
RUN sudo chown -R node:node /home/node/.opencodeExtend the customizations.vscode.extensions array:
{
"customizations": {
"vscode": {
"extensions": [
"sst-dev.opencode",
"dbaeumer.vscode-eslint",
"ms-python.python",
"golang.go",
"rust-lang.rust-analyzer"
]
}
}
}Add or override environment variables:
{
"containerEnv": {
"NODE_OPTIONS": "--max-old-space-size=8192",
"EDITOR": "vim",
"MY_CUSTOM_VAR": "value"
}
}The firewall script allows you to add custom domains through the allowed-domains.txt file.
Step 1: Create or copy the example file
# In your project's .devcontainer directory
cp allowed-domains.txt.example allowed-domains.txtStep 2: Add your domains (one per line)
# .devcontainer/allowed-domains.txt
myapi.example.com
cdn.example.com
database.mycompany.comStep 3: Rebuild your container
The firewall script automatically reads from /workspace/.devcontainer/allowed-domains.txt at startup.
The base container includes these hardcoded domains:
registry.npmjs.org- npm package registryapi.anthropic.com- Anthropic API for OpenCodesentry.io,statsig.anthropic.com,statsig.com- Error reporting and analyticsmarketplace.visualstudio.com,vscode.blob.core.windows.net,update.code.visualstudio.com- VS Code services- GitHub IP ranges (fetched dynamically)
Override the default path using the ALLOWED_DOMAINS environment variable:
{
"containerEnv": {
"ALLOWED_DOMAINS": "/custom/path/to/domains.txt"
}
}The firewall script is located at /usr/local/bin/init-firewall.sh. To modify it:
Option 1: Override in custom Dockerfile
FROM vegastyle/opencode-devcontainer-base:latest
COPY my-custom-firewall.sh /usr/local/bin/init-firewall.sh
RUN sudo chmod +x /usr/local/bin/init-firewall.shOption 2: Extend the existing script
Create custom-firewall.sh:
#!/bin/bash
# Run the base firewall first
sudo /usr/local/bin/init-firewall.sh
# Add your custom rules
sudo iptables -A OUTPUT -d $(dig +short my-custom-domain.com) -j ACCEPTThen use it in postStartCommand:
{
"postStartCommand": "bash custom-firewall.sh"
}Create a custom Dockerfile that extends the base:
FROM vegastyle/opencode-devcontainer-base:latest
# Install Python
USER root
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# Install Python packages
USER node
RUN pip3 install --user numpy pandas matplotlib
# Copy custom OpenCode settings
COPY --chown=node:node .opencode /home/node/.opencode
# Add custom shell aliases
RUN echo "alias py='python3'" >> /home/node/.zshrcThis DevContainer includes a default-deny firewall that only allows outbound connections to:
- GitHub - Web, API, and Git operations
- npm Registry - Package downloads
- Anthropic API - OpenCode AI requests
- VS Code Services - Extension marketplace, updates
- Monitoring - Sentry, Statsig (error reporting)
- DNS & SSH - For resolution and Git operations
- Local Network - Host access for Docker functionality
- Localhost - Loopback traffic
Warning: This container requires elevated network capabilities (
NET_ADMIN,NET_RAW) to configure the firewall. While this provides strong network isolation, it also means:
- Malicious code in the container could modify firewall rules
- The container has more privileges than a standard container
- Only use this with trusted repositories
NET_ADMIN- Allows the firewall script to configure iptables rulesNET_RAW- Enables packet filtering and network monitoring
- Never commit API keys - Use mounted volumes for
auth.json - Review the firewall script - Understand what connections are allowed
- Use with trusted code - Don't run untrusted code in this container
- Keep the base image updated - Pull the latest version regularly
- Monitor outbound connections - Check container logs for unexpected traffic
dev-containers/
├── .github/
│ └── workflows/
│ └── on_push.yml # Semantic versioning workflow
├── opencode/
│ └── .devcontainer/
│ ├── devcontainer.json # Base DevContainer configuration
│ ├── dockerfile # Base image build instructions
│ └── init-firewall.sh # Network isolation script
├── readme-examples/ # Example README files for reference
├── LICENSE # Apache 2.0 License
└── README.md # This file
dockerfile- Defines the base image with Node.js, OpenCode, and development toolsdevcontainer.json- Reference configuration for the DevContainerinit-firewall.sh- Configures iptables for network isolationon_push.yml- Automates version bumping using semantic versioning
- Image Pull/Build - Docker pulls
vegastyle/opencode-devcontainer-baseor builds from Dockerfile - Container Creation - VS Code creates the container with specified capabilities
- Volume Mounting - Persistent volumes for bash history and OpenCode config are attached
- User Switch - Container runs as non-root
nodeuser - Post-Start Command - Firewall script initializes network rules
- Extension Installation - VS Code installs specified extensions
- Ready - Environment is ready for development
The firewall script (init-firewall.sh) runs at startup and:
- Preserves DNS - Saves Docker DNS rules before flushing
- Creates IP Sets - Uses ipset for efficient CIDR matching
- Resolves Domains - Dynamically gets IPs for allowed domains
- Aggregates Ranges - Combines IPs into efficient CIDR blocks
- Applies Rules - Sets iptables to default-deny with whitelist
- Verifies - Tests that blocked domains fail and allowed domains succeed
OpenCode CLI is installed globally via npm and configured to:
- Store global settings in
/home/node/.config/opencode - Store user settings in
/home/node/.opencode(customizable) - Use environment variables for API keys (optional)
- Integrate with VS Code through the
sst-dev.opencodeextension
Create a custom Dockerfile:
FROM vegastyle/opencode-devcontainer-base:latest
USER root
# Install Python and common tools
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
python3-venv \
&& rm -rf /var/lib/apt/lists/*
USER node
# Install common Python packages
RUN pip3 install --user \
numpy \
pandas \
matplotlib \
jupyter \
pytestUpdate .devcontainer/devcontainer.json:
{
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"extensions": [
"sst-dev.opencode",
"ms-python.python",
"ms-python.vscode-pylance"
]
}
}
}Use Docker Compose (.devcontainer/docker-compose.yml):
version: '3.8'
services:
app:
image: vegastyle/opencode-devcontainer-base:latest
cap_add:
- NET_ADMIN
- NET_RAW
volumes:
- ..:/workspace:cached
command: sleep infinity
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: myapp
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:Update .devcontainer/devcontainer.json:
{
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace"
}Create .opencode/config.json in your project:
{
"defaultProvider": "anthropic",
"model": "claude-sonnet-4-5-20250929",
"maxTokens": 8000,
"temperature": 0.7,
"streaming": true
}Mount it in .devcontainer/devcontainer.json:
{
"mounts": [
"source=${localWorkspaceFolder}/.opencode,target=/home/node/.opencode,type=bind,consistency=cached"
]
}Create custom-firewall-additions.sh:
#!/bin/bash
# Get IP for custom domain
CUSTOM_DOMAIN="myapi.example.com"
CUSTOM_IPS=$(dig +short "$CUSTOM_DOMAIN" | grep -E '^[0-9.]+$')
# Add to firewall
for ip in $CUSTOM_IPS; do
sudo iptables -A OUTPUT -d "$ip" -j ACCEPT
echo "Allowed outbound to $CUSTOM_DOMAIN ($ip)"
doneUpdate .devcontainer/devcontainer.json:
{
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh && bash .devcontainer/custom-firewall-additions.sh"
}Symptom:
Verification failed! example.com should be blocked but was accessible.
Solution:
- The firewall script couldn't properly block traffic
- Check Docker network settings
- Ensure
NET_ADMINandNET_RAWcapabilities are granted - Try running
sudo /usr/local/bin/init-firewall.shmanually in the container
Symptom:
bash: opencode: command not found
Solution:
- OpenCode may not be installed properly
- Check if you're using the correct base image
- Verify the Dockerfile includes the OpenCode installation step
- Try running
npm list -g opencode-aito check installation
Symptom:
Error: API key not found or invalid
Solution:
- Verify your
auth.jsonfile exists and contains valid keys - Check the file is mounted correctly to
/home/node/.local/share/opencode/auth.json - Ensure the JSON syntax is correct (no trailing commas)
- Test your API key directly at the provider's website
Symptom:
Error: EACCES: permission denied
Solution:
- The container runs as the
nodeuser (non-root) - Files created on the host may have incorrect permissions
- Fix with:
sudo chown -R node:node /path/to/filesinside the container - Or on host:
chmod -R 755 /path/to/files
Symptom:
Error: connect ETIMEDOUT
Solution:
- The firewall blocks connections by default
- Check if the domain is in the whitelist in
init-firewall.sh - Add the domain to the firewall whitelist (see Customization Examples)
- Verify DNS resolution:
dig +short domain.com
Symptom: Container fails to start or exits immediately
Solution:
- Check Docker logs:
docker logs <container-id> - Verify Docker has enough resources (memory, disk space)
- Try removing old containers:
docker container prune - Check for conflicting ports
- Ensure Docker Desktop is running
Symptom:
VS Code extensions listed in devcontainer.json don't install
Solution:
- Check internet connection and firewall rules
- Verify
marketplace.visualstudio.comis allowed in firewall - Try installing manually:
Ctrl+Shift+X→ search → install - Check VS Code output panel for error messages
Contributions are welcome! Here's how you can help:
-
Report Issues - Found a bug or have a feature request? Open an issue
-
Submit Pull Requests
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m "Add my feature" - Push to the branch:
git push origin feature/my-feature - Open a Pull Request
-
Improve Documentation - Help make this README better by fixing typos, adding examples, or clarifying instructions
-
Share Examples - Created a cool customization? Share it in the discussions or add it to the examples section
- Follow existing code style and conventions
- Test your changes in a clean DevContainer environment
- Update documentation for any new features
- Add comments for complex logic
- Ensure the firewall script still works after modifications
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Made with ❤️ for developers who want secure, AI-assisted development environments
Questions? Issues? Open an issue or reach out to the community!