Compare commits

..

21 Commits

Author SHA1 Message Date
github-actions[bot] b27487d844 fix: update URLs from cpfarhood to farhoodliquor org 2026-03-22 09:43:13 -04:00
Chris Farhood 24b31e061a Delete CNAME 2026-03-22 08:59:24 -04:00
Chris Farhood aad4ec4287 Create CNAME 2026-03-22 08:59:01 -04:00
Chris Farhood 4915fce118 Delete CNAME 2026-03-22 08:58:54 -04:00
Chris Farhood 7940419c4e Create CNAME 2026-03-22 08:58:47 -04:00
github-actions[bot] 9ddbcc1e68 Publish chart 2.6.0 2026-03-11 12:15:44 +00:00
github-actions[bot] 07a6baefd7 Publish chart 2.4.0 2026-03-03 16:46:43 +00:00
github-actions[bot] 720e6d7de9 Publish chart 2.3.0 2026-03-01 15:32:02 +00:00
github-actions[bot] c89d7e4ed6 Publish chart 2.2.5 2026-02-28 19:41:38 +00:00
github-actions[bot] 80763b817d Publish chart 2.2.4 2026-02-28 19:40:18 +00:00
github-actions[bot] 2bb60745ce Publish chart 2.2.3 2026-02-28 17:49:23 +00:00
github-actions[bot] 46cef775b1 Publish chart 2.2.2 2026-02-28 16:12:27 +00:00
github-actions[bot] b83265f131 Publish chart 2.2.1 2026-02-28 14:25:39 +00:00
github-actions[bot] 695850469d Publish chart 2.2.0 2026-02-27 14:26:42 +00:00
github-actions[bot] eca8d10772 Publish chart 2.1.1 2026-02-27 02:46:40 +00:00
github-actions[bot] 0eaf1645e1 Publish chart 2.1.0 2026-02-27 02:11:32 +00:00
github-actions[bot] 8b8af85558 Publish chart 2.0.5 2026-02-27 01:02:59 +00:00
github-actions[bot] a9593a763f Publish chart 2.0.4 2026-02-25 23:03:26 +00:00
github-actions[bot] 129f49abe8 Publish chart 2.0.3 2026-02-25 16:48:57 +00:00
github-actions[bot] 58ee2d6770 Publish chart 2.0.1-dev 2026-02-25 15:34:55 +00:00
github-actions[bot] 955f26ab37 Publish chart 2.0.0-dev 2026-02-25 14:01:56 +00:00
67 changed files with 297 additions and 6218 deletions
-6
View File
@@ -1,6 +0,0 @@
{
"enabledPlugins": {
"voltagent-dev-exp@voltagent-subagents": true,
"voltagent-lang@voltagent-subagents": true
}
}
-16
View File
@@ -1,16 +0,0 @@
{
"enabledMcpjsonServers": [
"kubernetes",
"flux",
"playwright",
"github",
"pgtuner",
"fetch",
"sequentialthinking"
],
"permissions": {
"allow": [
"Bash(git add .claude/settings.local.json .claude/settings.json && git commit -m \"$\\(cat <<'EOF'\nchore: update Claude Code settings and enable voltagent plugins\n\nAdd fetch and sequentialthinking MCP servers to allowed list, and enable\nvoltagent dev-exp and lang subagent plugins.\n\nCo-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>\nEOF\n\\)\" && git status)"
]
}
}
-24
View File
@@ -1,24 +0,0 @@
.git
.gitignore
.dockerignore
*.md
README.md
LICENSE
Makefile
docker-compose.yml
# Kubernetes files
k8s/
# Local development
home/
workspace/
*.log
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
-35
View File
@@ -1,35 +0,0 @@
## Description
<!-- Describe your changes in detail -->
## Type of Change
<!-- Mark with an `x` all that apply -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
- [ ] CI/CD update
## Testing
<!-- Describe the tests you ran to verify your changes -->
- [ ] Built Docker image locally
- [ ] Tested container startup
- [ ] Tested repository cloning
- [ ] Tested VNC web interface
## Checklist
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have tested that the Docker image builds successfully
## Screenshots (if applicable)
<!-- Add screenshots to help explain your changes -->
-93
View File
@@ -1,93 +0,0 @@
# CI/CD Pipeline Guide
## 🚀 Simplified Pipeline - Only 3 Workflows!
### 1️⃣ For Releases → **Unified Release**
Use this for all version releases:
1. Go to [Actions → Unified Release](https://github.com/cpfarhood/devcontainer/actions/workflows/release-unified.yaml)
2. Click "Run workflow"
3. Either:
- Enter specific version (e.g., `0.2.1`), OR
- Choose release type (patch/minor/major) for auto-increment
4. Click "Run workflow"
**This single workflow does EVERYTHING:**
- ✅ Updates chart version
- ✅ Creates git tag
- ✅ Builds Docker image with all proper tags
- ✅ Publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`)
- ✅ Creates GitHub Release with changelog
### 2️⃣ For Quick Fixes → **Quick Fix Build**
Use this for emergency fixes without version changes:
1. Go to [Actions → Quick Fix Build](https://github.com/cpfarhood/devcontainer/actions/workflows/quick-fix.yaml)
2. Click "Run workflow"
3. Enter tag (default: `latest`)
4. Click "Run workflow"
**Just builds and pushes Docker image** - no version bumps, no releases.
### 3️⃣ Automatic CI → **Build and Push**
Runs automatically on:
- Pushes to `main` (builds and pushes; skipped for release commits via `[skip ci]`)
- Pull requests (builds but doesn't push)
- Manual trigger available
## Workflow Files
| Workflow | File | Purpose | When to Use |
|----------|------|---------|-------------|
| **Unified Release** | `release-unified.yaml` | Full release process | New versions |
| **Quick Fix Build** | `quick-fix.yaml` | Docker build only | Hotfixes |
| **Build and Push** | `build-and-push.yaml` | CI/CD automation | PRs & tags |
## Examples
### Release a new version
```bash
# Via GitHub UI (Recommended):
# Go to Actions → Unified Release → Run workflow
# Via GitHub CLI:
gh workflow run release-unified.yaml -f version=0.2.1
# OR auto-increment:
gh workflow run release-unified.yaml -f release_type=patch
```
### Push a quick fix
```bash
# Via GitHub UI:
# Go to Actions → Quick Fix Build → Run workflow
# Via GitHub CLI:
gh workflow run quick-fix.yaml -f tag=hotfix-1
```
### Check workflow status
```bash
# List all recent runs
gh run list --limit 5
# Watch a specific workflow
gh run watch
```
## Version Strategy
- **Major** (1.0.0): Breaking changes
- **Minor** (0.2.0): New features
- **Patch** (0.2.1): Bug fixes
## What We Fixed
### Before (Nightmare 😱)
- Auto-version-bump with `[skip ci]` prevented Docker builds
- 6+ disconnected workflows
- Manual tag deletion and re-pushing
- Version conflicts everywhere
### After (Simple! 🎉)
- **3 total workflows** (down from 6+)
- **1 button** for complete releases
- Release builds its own Docker image — `[skip ci]` on the version commit prevents duplicate CI builds
- **Clear separation** of concerns
-61
View File
@@ -1,61 +0,0 @@
name: Build and Push Docker Image
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
if: >-
github.event_name != 'push'
|| !contains(github.event.head_commit.message, '[skip ci]')
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
-56
View File
@@ -1,56 +0,0 @@
name: Quick Fix Build
on:
workflow_dispatch:
inputs:
tag:
description: 'Tag for the image (defaults to latest)'
required: false
default: 'latest'
type: string
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
- name: Summary
run: |
echo "## ✅ Quick Fix Build Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Images Published:" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
-192
View File
@@ -1,192 +0,0 @@
name: Unified Release
on:
workflow_dispatch:
inputs:
version:
description: 'Explicit version (e.g., 1.2.3). Leave blank to auto-increment.'
required: false
type: string
release_type:
description: 'Release type (used when version is blank)'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up Helm
uses: azure/setup-helm@v4
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Determine Version
id: version
run: |
INPUT_VERSION="${{ github.event.inputs.version }}"
if [ -n "$INPUT_VERSION" ]; then
VERSION="$INPUT_VERSION"
else
# Auto-increment based on release_type
CURRENT=$(grep '^version:' chart/Chart.yaml | awk '{print $2}')
# Strip any pre-release suffix (e.g., 2.0.0-dev -> 2.0.0)
CURRENT=$(echo "$CURRENT" | sed 's/-.*//')
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
MINOR=$(echo "$CURRENT" | cut -d. -f2)
PATCH=$(echo "$CURRENT" | cut -d. -f3)
case "${{ github.event.inputs.release_type }}" in
major) VERSION="$((MAJOR + 1)).0.0" ;;
minor) VERSION="${MAJOR}.$((MINOR + 1)).0" ;;
patch) VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" ;;
esac
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
echo "Releasing version ${VERSION}"
- name: Update Chart Version
run: |
sed -i "s/^version: .*/version: ${{ steps.version.outputs.version }}/" chart/Chart.yaml
git add chart/Chart.yaml
git diff --quiet --staged || git commit -m "chore(release): ${{ steps.version.outputs.version }} [skip ci]"
- name: Create and Push Tag
run: |
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
git push origin main
git push origin "${{ steps.version.outputs.tag }}"
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
context: .
push: true
no-cache: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-to: type=gha,mode=max
platforms: linux/amd64
- name: Publish Helm Chart to GitHub Pages
run: |
helm package chart/
CHART_TGZ="devcontainer-${{ steps.version.outputs.version }}.tgz"
# Set up gh-pages in a temporary directory
PAGES_DIR=$(mktemp -d)
if git ls-remote --heads origin gh-pages | grep -q gh-pages; then
# gh-pages exists — shallow clone just that branch
git clone --single-branch --branch gh-pages \
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \
"$PAGES_DIR"
else
# First time — initialize gh-pages
git init "$PAGES_DIR"
git -C "$PAGES_DIR" checkout --orphan gh-pages
git -C "$PAGES_DIR" remote add origin \
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
cat > "$PAGES_DIR/index.html" <<'HTMLEOF'
<!DOCTYPE html>
<html>
<head><title>Dev Container Helm Chart Repository</title></head>
<body>
<h1>Dev Container Helm Chart Repository</h1>
<p>Add this repository to Helm:</p>
<pre>helm repo add devcontainer https://cpfarhood.github.io/devcontainer</pre>
<p>Install the chart:</p>
<pre>helm install mydev devcontainer/devcontainer --set name=mydev</pre>
</body>
</html>
HTMLEOF
fi
git -C "$PAGES_DIR" config user.name "github-actions[bot]"
git -C "$PAGES_DIR" config user.email "github-actions[bot]@users.noreply.github.com"
# Copy chart package and rebuild index
cp "$CHART_TGZ" "$PAGES_DIR/"
if [ -f "$PAGES_DIR/index.yaml" ]; then
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer --merge "$PAGES_DIR/index.yaml"
else
helm repo index "$PAGES_DIR" --url https://cpfarhood.github.io/devcontainer
fi
# Commit and push
git -C "$PAGES_DIR" add .
git -C "$PAGES_DIR" commit -m "Publish chart ${{ steps.version.outputs.version }}"
git -C "$PAGES_DIR" push origin gh-pages
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
TAG: ${{ steps.version.outputs.tag }}
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.tag }}
run: |
PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
COMMITS=$(git log --pretty=format:"- %s (%h)" HEAD)
else
COMMITS=$(git log --pretty=format:"- %s (%h)" "${PREV_TAG}..HEAD")
fi
cat > release-notes.md <<NOTESEOF
## Release ${VERSION}
### Changes
${COMMITS}
### Docker Image
\`\`\`bash
docker pull ${IMAGE}
\`\`\`
### Helm Chart
\`\`\`bash
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
helm repo update
helm install mydev devcontainer/devcontainer --version ${VERSION} --set name=mydev
\`\`\`
NOTESEOF
sed -i 's/^ //' release-notes.md
gh release create "${TAG}" \
--title "Release ${TAG}" \
--notes-file release-notes.md
-24
View File
@@ -1,24 +0,0 @@
# Secrets
*.env
.env.local
secrets.yaml
k8s/sealedsecrets.yaml
# Local volumes
home/
workspace/
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Build artifacts
*.tar
*.tar.gz
-27
View File
@@ -1,27 +0,0 @@
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${GITHUB_TOKEN}"
}
},
"kubernetes": {
"type": "sse",
"url": "http://localhost:8080/sse"
},
"flux": {
"type": "sse",
"url": "http://localhost:8081/sse"
},
"playwright": {
"type": "sse",
"url": "http://localhost:8086/sse"
},
"helm": {
"type": "sse",
"url": "http://localhost:8012/sse"
}
}
}
-98
View File
@@ -1,98 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Initial project setup
- Antigravity IDE (VSCode) with web-based VNC access
- Happy Coder AI assistant integration
- Automatic GitHub repository cloning on startup
- Persistent home directory with ReadWriteMany PVC support
- Secure non-root execution (claude user, UID 1000, GID 1000)
- Support for private repositories via GitHub token
- HTTPRoute (Gateway API) support
- VNC password protection
- Multi-platform Docker image builds
- GitHub Actions CI/CD pipeline
- Automated releases on version tags
- Comprehensive deployment documentation (DEPLOYMENT.md)
- Complete variables reference (VARIABLES.md)
### Container Features
- Base: jlesage/baseimage-gui:ubuntu-22.04-v4
- Antigravity IDE (VSCode)
- Happy Coder npm package
- Chrome browser
- Node.js (LTS)
- Python 3
- Git
### Kubernetes Resources
- StatefulSet with volumeClaimTemplates
- ReadWriteMany PVC for /home directory
- ConfigMap for configuration
- Sealed Secrets support
- HTTPRoute for external access
- Service (headless)
### Configuration Options
- GitHub repository URL (required)
- GitHub token (optional, for private repos)
- VNC password (optional)
- Happy Coder server URL (optional)
- Happy Coder webapp URL (optional)
- Display resolution (configurable)
- Resource limits (configurable)
- Storage size (configurable)
### Documentation
- README.md with quick start guide
- DEPLOYMENT.md with step-by-step instructions
- VARIABLES.md with complete variable reference
- Release process documentation
- Pull request template
- Dependabot configuration
## Version History
No releases yet. See [Unreleased] section above for planned v1.0.0 features.
---
## Release Template
Use this template for future releases:
```markdown
## [1.0.0] - YYYY-MM-DD
### Added
- New features
- New configuration options
### Changed
- Changes to existing features
- Updated dependencies
### Deprecated
- Features that will be removed in future versions
### Removed
- Removed features
- Breaking changes
### Fixed
- Bug fixes
- Security patches
### Security
- Security improvements
- Vulnerability fixes
```
[Unreleased]: https://github.com/cpfarhood/devcontainer/compare/v1.0.0...HEAD
-211
View File
@@ -1,211 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
The Dev Container is a Docker-based cloud development environment that provides:
- Web-based GUI IDE (VSCode/Antigravity) via VNC on port 5800
- Claude Code, OpenCode, and Crush AI coding agents (terminal-based)
- Built-in web file manager for uploading/downloading files (optional, via `fileManager.enabled`)
- Automatic GitHub repository cloning on startup
- Kubernetes-native deployment with persistent home storage
- MCP (Model Context Protocol) sidecars for AI assistant integrations
The stack is primarily **Bash scripts + YAML** — there is no Node.js package, compiled language, or test framework.
## Common Commands
### Building
```bash
make build # Build Docker image
make build REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0 # Custom registry/tag
docker build -t ghcr.io/cpfarhood/devcontainer:latest . # Direct build
```
### Running Locally
```bash
GITHUB_REPO="https://github.com/user/repo" make run # Run with Docker
make stop # Stop container
make clean # Remove volumes
```
### Kubernetes Deployment
```bash
GITHUB_REPO="https://github.com/user/repo" make helm-deploy # Deploy with Helm
make helm-delete # Tear down Helm release
make helm-port-forward # Forward port 5800 to localhost
make helm-logs # Stream container logs
make helm-shell # Open interactive shell in pod
# Or use Helm directly
helm install mydev ./chart --set name=mydev --set githubRepo=https://github.com/user/repo
```
### Other Useful Targets
```bash
make help # List all Makefile targets with descriptions
make push # Push image to registry (build first)
```
## Architecture
### Startup Flow
```
Container start
→ scripts/startapp.sh
→ scripts/init-repo.sh
→ Configure git user & credentials
→ Clone GITHUB_REPO (if set)
→ Launch VSCode as user `user` in /workspace
```
### Key Files
| File | Purpose |
|------|---------|
| `Dockerfile` | Image definition — installs Chrome, VSCode, Helm, gh CLI, kubeseal, Claude Code, OpenCode, Crush; creates non-root user (UID 1000) |
| `scripts/init-repo.sh` | Configures git credentials, clones GitHub repo |
| `scripts/startapp.sh` | Calls init-repo.sh then opens VSCode in the workspace |
| `chart/` | Helm chart for Kubernetes deployment |
| `chart/templates/deployment.yaml` | Deployment spec — main container + MCP sidecar containers |
| `chart/templates/rbac.yaml` | ServiceAccount, Role/ClusterRole based on `clusterAccess` value |
| `chart/templates/pvc.yaml` | PersistentVolumeClaim for user home |
| `chart/templates/service.yaml` | ClusterIP Service (VNC + optional SSH) |
| `chart/values.yaml` | Default Helm values |
| `.mcp.json` | MCP server connection config (GitHub Copilot, Kubernetes, Flux, Helm, Fetch, Sequential Thinking, Playwright, pgtuner) |
| `Makefile` | Build/deploy automation |
### MCP Sidecars
MCP (Model Context Protocol) servers run as sidecar containers in the pod, enabling AI assistants to interact with various services:
| Sidecar | Image | Version | Port | Endpoint | Default |
|---------|-------|---------|------|----------|---------|
| `kubernetes-mcp` | `quay.io/containers/kubernetes_mcp_server` | v0.0.57 | 8080 | `http://localhost:8080/sse` | Enabled |
| `flux-mcp` | `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp` | v0.41.1 | 8081 | `http://localhost:8081/sse` | Enabled |
| `helm-mcp` | `ghcr.io/zekker6/mcp-helm` | v1.3.1 | 8012 | `http://localhost:8012/sse` | Enabled |
| `fetch-mcp` | `mcp/fetch` | latest | 8082 | `http://localhost:8082/sse` | Enabled |
| `sequentialthinking-mcp` | `mcp/sequentialthinking` | latest | 8083 | `http://localhost:8083/sse` | Enabled |
| `homeassistant-mcp` | `ghcr.io/homeassistant-ai/ha-mcp` | stable | 8087 | `http://localhost:8087/sse` | Disabled |
| `pgtuner-mcp` | `dog830228/pgtuner_mcp` | latest | 8085 | `http://localhost:8085/sse` | Disabled |
| `playwright-mcp` | `mcr.microsoft.com/playwright/mcp` | latest | 8086 | `http://localhost:8086/sse` | Enabled |
**Note:**
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (they need RBAC permissions)
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions
- Helm sidecar enables browsing Helm repositories and chart metadata
- Fetch sidecar provides web content fetching capabilities and HTML to markdown conversion
- Sequential thinking sidecar enables structured thinking and problem-solving processes
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
- PostgreSQL tuner sidecar requires `DATABASE_URI` in the env secret (PostgreSQL connection string)
- Playwright sidecar provides browser automation and web testing capabilities
#### Enabling/Disabling MCP Servers
To control MCP sidecars, set the `enabled` flag in your values override:
```yaml
# Disable all MCP sidecars
mcp:
sidecars:
kubernetes:
enabled: false
flux:
enabled: false
helm:
enabled: false
fetch:
enabled: false
sequentialthinking:
enabled: false
homeassistant:
enabled: false
pgtuner:
enabled: false
playwright:
enabled: false
# Or selectively enable/disable
mcp:
sidecars:
kubernetes:
enabled: true # Keep Kubernetes MCP enabled
flux:
enabled: false # Disable Flux MCP
helm:
enabled: true # Enable Helm MCP for chart browsing
fetch:
enabled: true # Enable Fetch MCP for web content fetching
sequentialthinking:
enabled: true # Enable Sequential Thinking MCP for problem-solving
homeassistant:
enabled: true # Enable Home Assistant MCP (requires secrets)
pgtuner:
enabled: true # Enable PostgreSQL tuner MCP (requires DATABASE_URI)
playwright:
enabled: true # Enable Playwright MCP for browser automation
```
When deploying via Helm:
```bash
# Quick start (recommended)
cp chart/values-quickstart.yaml my-values.yaml
# Edit name and githubRepo in my-values.yaml
helm install my-devcontainer ./chart -f my-values.yaml
# Using --set flags
helm install my-devcontainer ./chart \
--set name=mydev \
--set githubRepo=https://github.com/user/repo \
--set mcp.sidecars.kubernetes.enabled=false
# Full customization
helm install my-devcontainer ./chart -f custom-values.yaml
```
### Storage Model
- `/config` — ReadWriteMany PVC (persists across pod restarts, holds user config/dotfiles)
- `/workspace` — emptyDir by default (ephemeral; can be changed to PVC)
### Environment Variables
**Required:**
- `GITHUB_REPO` — URL of repository to clone into `/workspace`
**Optional:**
- `GITHUB_TOKEN` — PAT for private repo access (automatically configures git credentials)
- `GIT_USER_NAME` — Git user name for commits (default: "DevContainer User")
- `GIT_USER_EMAIL` — Git user email for commits (default: "devcontainer@example.com")
- `GITLAB_HOST` — GitLab hostname if using GitLab with same token
- `VNC_PASSWORD` — VNC web interface password
- `DISPLAY_WIDTH` / `DISPLAY_HEIGHT` — VNC resolution
- `USER_ID` / `GROUP_ID` — Override UID/GID (default 1000)
- `WEB_FILE_MANAGER` — Set to `1` to enable the built-in web file manager (controlled via `fileManager.enabled` in Helm values)
- `WEB_FILE_MANAGER_ALLOWED_PATHS` — Paths accessible by the file manager (default: `/workspace,/config`)
- `WEB_FILE_MANAGER_DENIED_PATHS` — Paths to deny access to (takes precedence over allowed)
### CI/CD
- **`build-and-push.yaml`** — Builds and pushes to GHCR on every push to `main`, version tags (`v*`), and PRs. Tags: `latest` (main), semver, branch name, commit SHA.
- **`release-unified.yaml`** — Manual release workflow: bumps chart version, builds Docker image, publishes Helm chart to GitHub Pages (`https://cpfarhood.github.io/devcontainer`), and creates GitHub Release.
- **`dependabot.yml`** — Weekly updates for GitHub Actions and Docker base image.
Image registry: `ghcr.io/cpfarhood/devcontainer`
Helm repo: `https://cpfarhood.github.io/devcontainer`
## Kubernetes Notes
- Deployed via Helm chart (`chart/`), published to GitHub Pages Helm repo, reconciled by Flux
- Storage class is `ceph-filesystem` by default — change via `storage.className` in values
- Resource limits: 14 CPU, 28Gi memory
- Health checks (liveness/readiness probes) on port 5800
- Secrets: optional env Secret (`devcontainer-{name}-secrets-env`) for `GITHUB_TOKEN`, `VNC_PASSWORD`, etc.
- RBAC: controlled by `clusterAccess` value (`none`, `readonlyns`, `readwritens`, `readonly`, `readwrite`)
-437
View File
@@ -1,437 +0,0 @@
# Deployment Guide
This guide provides step-by-step instructions for deploying the Antigravity Dev Container using Helm.
## Prerequisites
- Kubernetes cluster (1.19+)
- `kubectl` configured to access your cluster
- `helm` CLI installed (3.0+)
- ReadWriteMany storage class available (e.g., `ceph-filesystem`, `nfs-client`, `efs-sc`)
- GitHub Container Registry access (images are public)
## Quick Start
### 1. Clone the Repository
```bash
git clone https://github.com/cpfarhood/devcontainer.git
cd devcontainer
```
### 2. Create Secret (Optional)
For private repos or VNC password:
```bash
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=VNC_PASSWORD='changeme' \
--from-literal=ANTHROPIC_API_KEY='sk-ant-...'
```
### 3. Deploy with Helm
```bash
# Basic deployment
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
# With custom storage class
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set storage.className=nfs-client
# With cluster access for kubectl
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set clusterAccess=readwritens
```
### 4. Access the Container
```bash
# Port forward
kubectl port-forward deployment/devcontainer-mydev 5800:5800
open http://localhost:5800
```
## Deployment Options
### Using Values File
Create a custom `values.yaml`:
```yaml
name: mydev
githubRepo: https://github.com/youruser/yourrepo
ide: vscode
ssh: false
# Storage
storage:
size: 32Gi
className: ceph-filesystem
# Resources
resources:
requests:
memory: "4Gi"
cpu: "2000m"
limits:
memory: "16Gi"
cpu: "8000m"
# Kubernetes access
clusterAccess: readwritens
# MCP sidecars
mcpSidecars:
kubernetes:
enabled: true
flux:
enabled: false
```
Deploy:
```bash
helm install mydev ./chart -f values.yaml
```
### SSH Access Setup
Enable SSH and add your public key:
```bash
# Create secret with SSH key
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...'
# Deploy with SSH enabled
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set ssh=true
# Connect via SSH
kubectl port-forward deployment/devcontainer-mydev 2222:22
ssh -p 2222 user@localhost
```
### MCP Sidecar Configuration
Control MCP servers for AI-assisted operations.
**Important:** Kubernetes and Flux MCP sidecars are only deployed when:
1. They are enabled in values (`mcpSidecars.<name>.enabled: true`)
2. AND `clusterAccess` is not `none` (they need RBAC permissions to function)
```bash
# Disable all MCP sidecars
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.kubernetes.enabled=false \
--set mcpSidecars.flux.enabled=false \
--set mcpSidecars.homeassistant.enabled=false
# Enable only Kubernetes MCP
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.kubernetes.enabled=true \
--set mcpSidecars.flux.enabled=false
# Enable Home Assistant MCP (requires credentials)
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=homeassistant-url='http://homeassistant.local:8123' \
--from-literal=homeassistant-token='your_long_lived_token'
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcpSidecars.homeassistant.enabled=true
```
### Cluster Access Levels
Configure Kubernetes RBAC permissions:
| Value | Scope | Permissions | Use Case |
|-------|-------|-------------|----------|
| `none` | No access | None | Default, isolated development |
| `readonlyns` | Namespace | Read-only | View resources in namespace |
| `readwritens` | Namespace | Full access | Deploy apps in namespace |
| `readonly` | Cluster-wide | Read-only | View all cluster resources |
| `readwrite` | Cluster-wide | Full access | Cluster administration |
```bash
# Example: Full access within namespace
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set clusterAccess=readwritens
```
## Ingress Configuration
### Using Gateway API HTTPRoute
Create an HTTPRoute for external access:
```yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: devcontainer-mydev
spec:
parentRefs:
- name: your-gateway
namespace: your-gateway-namespace
hostnames:
- devcontainer.example.com
rules:
- backendRefs:
- name: devcontainer-mydev
port: 5800
```
### Using Traditional Ingress
Create an Ingress resource:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: devcontainer-mydev
spec:
rules:
- host: devcontainer.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: devcontainer-mydev
port:
number: 5800
```
## Advanced Configurations
### Custom Display Resolution
```bash
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set display.width=2560 \
--set display.height=1440
```
### Different IDE Options
```bash
# Use Google Antigravity
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set ide=antigravity
# SSH-only mode (no GUI)
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set ide=none \
--set ssh=true
```
## Helm Operations
### List Deployments
```bash
helm list
```
### Upgrade Deployment
```bash
# Change values
helm upgrade mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/newrepo
# Upgrade with new chart version
git pull
helm upgrade mydev ./chart
```
### Uninstall
```bash
helm uninstall mydev
# Note: PVC persists by default
kubectl delete pvc userhome-mydev
```
### Rollback
```bash
# View history
helm history mydev
# Rollback to previous version
helm rollback mydev
# Rollback to specific revision
helm rollback mydev 3
```
## Troubleshooting
### Pod Not Starting
```bash
# Check pod status
kubectl get pods -l app.kubernetes.io/instance=mydev
# Describe pod for events
kubectl describe pod -l app.kubernetes.io/instance=mydev
# Check logs
kubectl logs deployment/devcontainer-mydev
```
### Repository Not Cloning
```bash
# Check init logs
kubectl logs deployment/devcontainer-mydev | grep "Repository Initialization"
# Verify secret exists
kubectl get secret devcontainer-mydev-secrets-env
# Check environment
kubectl exec deployment/devcontainer-mydev -- env | grep GITHUB
```
### VNC Not Accessible
```bash
# Check service
kubectl get svc devcontainer-mydev
kubectl describe svc devcontainer-mydev
# Test with port-forward
kubectl port-forward deployment/devcontainer-mydev 5800:5800
```
### MCP Sidecar Issues
```bash
# Check all containers
kubectl get pod -l app.kubernetes.io/instance=mydev -o jsonpath='{.items[0].spec.containers[*].name}'
# Check MCP container logs
kubectl logs deployment/devcontainer-mydev -c kubernetes-mcp
kubectl logs deployment/devcontainer-mydev -c flux-mcp
kubectl logs deployment/devcontainer-mydev -c homeassistant-mcp
# Verify RBAC permissions (for Kubernetes/Flux MCP)
kubectl auth can-i --list --as system:serviceaccount:default:devcontainer-mydev
# Check Home Assistant MCP credentials
kubectl get secret devcontainer-mydev-secrets-env -o jsonpath='{.data.homeassistant-url}' | base64 -d
# Verify the URL is accessible from the pod
kubectl exec deployment/devcontainer-mydev -- curl -s http://homeassistant.local:8123/api/
```
### Storage Issues
```bash
# Check PVC
kubectl get pvc userhome-mydev
kubectl describe pvc userhome-mydev
# Check available storage classes
kubectl get storageclass
# Verify ReadWriteMany support
kubectl get storageclass <class-name> -o yaml | grep -i accessmodes
```
## Best Practices
### Production Deployment
1. **Use specific image tags** instead of `latest`:
```bash
helm install mydev ./chart --set image.tag=v1.0.0
```
2. **Set resource limits** appropriately:
```yaml
resources:
requests:
memory: "4Gi"
cpu: "2000m"
limits:
memory: "8Gi"
cpu: "4000m"
```
3. **Enable VNC password**:
```bash
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=VNC_PASSWORD='strong-password-here'
```
4. **Use dedicated namespace**:
```bash
kubectl create namespace dev-environments
helm install mydev ./chart -n dev-environments
```
5. **Configure appropriate cluster access**:
- Use `readonlyns` or `readwritens` for namespace-scoped work
- Avoid `readwrite` cluster-wide access unless necessary
### Multi-User Deployment
For teams, create separate deployments per user:
```bash
# User 1
helm install alice-dev ./chart \
--set name=alice-dev \
--set githubRepo=https://github.com/alice/project
# User 2
helm install bob-dev ./chart \
--set name=bob-dev \
--set githubRepo=https://github.com/bob/project
```
### Backup and Recovery
The home directory persists on PVC. To backup:
```bash
# Create backup pod
kubectl run backup --image=busybox --restart=Never --rm -i --tty \
-- tar czf - -C /home . | gzip > home-backup.tar.gz
```
## Support
For issues or questions:
- GitHub Issues: https://github.com/cpfarhood/devcontainer/issues
- Documentation: https://github.com/cpfarhood/devcontainer
-180
View File
@@ -1,180 +0,0 @@
FROM jlesage/baseimage-gui:ubuntu-22.04-v4
# Set environment variables
ENV APP_NAME="Dev Container" \
KEEP_APP_RUNNING=1 \
DISPLAY_WIDTH=1920 \
DISPLAY_HEIGHT=1080 \
SECURE_CONNECTION=1 \
USER_ID=1000 \
GROUP_ID=1000 \
CLAUDE_USER=user
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
wget \
gnupg \
ca-certificates \
git \
build-essential \
python3 \
python3-pip \
jq \
unzip \
sudo \
&& rm -rf /var/lib/apt/lists/*
# Install Chrome and xdg-utils (needed for xdg-open to work in VNC)
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome-keyring.gpg && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \
apt-get update && \
apt-get install -y google-chrome-stable xdg-utils && \
rm -rf /var/lib/apt/lists/*
# Chrome wrapper: adds flags required for running inside a Docker container.
# xdg-open (used by Claude Code on Linux) respects $BROWSER, so pointing it
# here ensures the OAuth popup works without manual --no-sandbox invocations.
# Cleans up crash lock files and suppresses the crash-restore bubble so that
# sessions/cookies survive unclean pod shutdowns (SIGKILL).
RUN printf '#!/bin/bash\n\
CHROME_DIR="/config/userdata/.config/google-chrome"\n\
mkdir -p "$CHROME_DIR"\n\
# Remove stale lock files left by unclean container shutdown\n\
rm -f "$CHROME_DIR/SingletonLock" "$CHROME_DIR/SingletonSocket" "$CHROME_DIR/SingletonCookie"\n\
# Mark the previous session as clean so Chrome does not clear cookies\n\
PREFS="$CHROME_DIR/Default/Preferences"\n\
if [ -f "$PREFS" ]; then\n\
sed -i '\''s/"exit_type":"Crashed"/"exit_type":"Normal"/g; s/"exited_cleanly":false/"exited_cleanly":true/g'\'' "$PREFS"\n\
fi\n\
exec /usr/bin/google-chrome-stable \\\n\
--no-sandbox \\\n\
--disable-dev-shm-usage \\\n\
--disable-gpu \\\n\
--disable-session-crashed-bubble \\\n\
--user-data-dir="$CHROME_DIR" \\\n\
"$@"\n' > /usr/local/bin/google-chrome && \
chmod +x /usr/local/bin/google-chrome
# Install Claude Code native binary (npm wrapper breaks remote control)
RUN curl -fsSL https://claude.ai/install.sh | bash && \
cp /root/.local/bin/claude /usr/local/bin/claude && \
rm -rf /root/.local/bin/claude && \
claude --version
# Disable Claude Code auto-updater (doesn't work inside Docker)
RUN mkdir -p /etc/skel/.claude && \
echo '{"env":{"DISABLE_AUTOUPDATER":"1"}}' > /etc/skel/.claude/settings.json
# Install OpenCode AI coding agent
RUN OPENCODE_VERSION=$(curl -sL https://api.github.com/repos/opencode-ai/opencode/releases/latest | jq -r '.tag_name') && \
curl -fsSL "https://github.com/opencode-ai/opencode/releases/download/${OPENCODE_VERSION}/opencode-linux-x86_64.tar.gz" | \
tar -xz -C /usr/local/bin opencode && \
chmod +x /usr/local/bin/opencode
# Install Crush AI coding agent (OpenCode successor by Charm)
RUN CRUSH_VERSION=$(curl -sL https://api.github.com/repos/charmbracelet/crush/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
curl -fsSL "https://github.com/charmbracelet/crush/releases/download/v${CRUSH_VERSION}/crush_${CRUSH_VERSION}_Linux_x86_64.tar.gz" -o /tmp/crush.tar.gz && \
tar -xzf /tmp/crush.tar.gz -C /tmp && \
mv /tmp/crush_${CRUSH_VERSION}_Linux_x86_64/crush /usr/local/bin/crush && \
chmod +x /usr/local/bin/crush && \
rm -rf /tmp/crush*
# Install Helm CLI for Kubernetes chart management
ARG HELM_VERSION=3.17.1
RUN curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | \
tar -xz --strip-components=1 -C /usr/local/bin linux-amd64/helm && \
chmod +x /usr/local/bin/helm
# Install GitHub CLI (gh) via official APT repo
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && \
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && \
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \
apt-get update && \
apt-get install -y gh && \
rm -rf /var/lib/apt/lists/*
# Install kubeseal CLI for Bitnami Sealed Secrets
RUN KUBESEAL_VERSION=$(curl -sL https://api.github.com/repos/bitnami-labs/sealed-secrets/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \
curl -fsSL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz" | \
tar -xz -C /usr/local/bin kubeseal && \
chmod +x /usr/local/bin/kubeseal
# Install VSCode (using Microsoft's current recommended setup)
RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg && \
install -D -o root -g root -m 644 /tmp/microsoft.gpg /usr/share/keyrings/microsoft.gpg && \
rm -f /tmp/microsoft.gpg && \
printf 'Types: deb\nURIs: https://packages.microsoft.com/repos/code\nSuites: stable\nComponents: main\nArchitectures: amd64\nSigned-By: /usr/share/keyrings/microsoft.gpg\n' \
> /etc/apt/sources.list.d/vscode.sources && \
apt-get update && \
apt-get install -y code && \
rm -rf /var/lib/apt/lists/*
# Install Google Antigravity IDE
RUN mkdir -p /etc/apt/keyrings && \
curl -fsSL https://us-central1-apt.pkg.dev/doc/repo-signing-key.gpg | \
gpg --dearmor --yes -o /etc/apt/keyrings/antigravity-repo-key.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/antigravity-repo-key.gpg] https://us-central1-apt.pkg.dev/projects/antigravity-auto-updater-dev/ antigravity-debian main" \
> /etc/apt/sources.list.d/antigravity.list && \
# Clear package cache to force fresh repository data
rm -rf /var/lib/apt/lists/* && \
apt-get update && \
# Show available versions for debugging
apt-cache policy antigravity && \
# Install latest version
apt-get install -y --no-install-recommends antigravity && \
# Display installed version
dpkg -l | grep antigravity && \
rm -rf /var/lib/apt/lists/*
# Pre-configure Antigravity to skip onboarding/setup on first run
RUN mkdir -p /etc/skel/.config/antigravity/User/globalStorage && \
echo '{"antigravityUnifiedStateSync.seenNuxOneTimeMigration": true, "antigravityUnifiedStateSync.browserOnboarding.completed": true, "antigravityUnifiedStateSync.hasOnboardingCompleted": true, "browserOnboarding.hasSeenWelcome": true, "antigravityUnifiedStateSync.browserPreferences.hasAddedLocalhostToAllowlist": true, "antigravityUnifiedStateSync.oauthToken.hasLegacyMigrated": true, "antigravityUnifiedStateSync.auth.tokenSyncEnabled": true, "antigravityUnifiedStateSync.auth.cloudSyncEnabled": true, "theme": "vs-dark"}' \
> /etc/skel/.config/antigravity/User/globalStorage/storage.json && \
echo '{"workbench.startupEditor": "none", "workbench.welcomePage.walkthroughs.openOnInstall": false, "workbench.tips.enabled": false, "extensions.ignoreRecommendations": true, "telemetry.telemetryLevel": "off", "update.mode": "none", "extensions.autoUpdate": false, "extensions.autoCheckUpdates": false, "workbench.enableExperiments": true, "workbench.settings.enableNaturalLanguageSearch": true, "antigravity.onboarding.completed": true, "antigravity.browserOnboarding.completed": true, "antigravity.setup.completed": true, "antigravity.ai.enabled": true, "antigravity.ai.autoComplete.enabled": true, "antigravity.ai.chat.enabled": true, "antigravity.ai.codeActions.enabled": true, "antigravity.ai.explainCode.enabled": true, "antigravity.ai.generateCode.enabled": true, "antigravity.ai.optimizeCode.enabled": true, "antigravity.ai.autoSuggest.enabled": true, "antigravity.telemetry.crashReporter": "on", "antigravity.ai.acceptTerms": true, "antigravity.auth.syncState": true, "antigravity.auth.enableTokenSync": true, "antigravity.ai.enableCloudSync": true, "antigravity.settings.sync": true}' \
> /etc/skel/.config/antigravity/User/settings.json && \
# Validate Antigravity installation
/usr/share/antigravity/antigravity --version || echo "WARNING: Antigravity version check failed"
# Install OpenSSH server (for SSH IDE mode)
RUN apt-get update && \
apt-get install -y openssh-server && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /var/run/sshd && \
sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config && \
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config && \
sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config && \
echo "PermitRootLogin no" >> /etc/ssh/sshd_config
# Create user user with specific UID/GID
RUN groupadd -g 1000 user && \
useradd -u 1000 -g 1000 -m -s /bin/bash user && \
echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# Create workspace directory
RUN mkdir -p /workspace && \
chown -R user:user /workspace
# Copy startup scripts
COPY --chmod=755 scripts/startapp.sh /startapp.sh
COPY --chmod=755 scripts/init-repo.sh /usr/local/bin/init-repo
# Copy serverless scripts (conditional execution)
COPY --chmod=755 serverless/scripts/dynamic-init-repo.sh /usr/local/bin/dynamic-init-repo
COPY --chmod=755 serverless/scripts/serverless-startapp.sh /usr/local/bin/serverless-startapp
# Fix app user shell after baseimage-gui creates it at runtime
COPY --chmod=755 scripts/cont-init-user.sh /etc/cont-init.d/20-fix-user-shell.sh
COPY --chmod=755 scripts/cont-init-sshd.sh /etc/cont-init.d/25-start-sshd.sh
# Set working directory
WORKDIR /workspace
# Configure container to run as user user
ENV HOME=/config/userdata \
USER=user \
BROWSER=/usr/local/bin/google-chrome
# Expose VNC port (baseimage-gui default)
EXPOSE 5800
# Set app name for baseimage-gui
RUN set-cont-env APP_NAME "Dev Container"
-119
View File
@@ -1,119 +0,0 @@
.PHONY: build push run stop clean help
# Variables
REGISTRY ?= ghcr.io/cpfarhood
IMAGE_NAME ?= devcontainer
IMAGE_TAG ?= latest
FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
.DEFAULT_GOAL := help
# Build the Docker image
build:
@echo "Building $(FULL_IMAGE)..."
docker build -t $(FULL_IMAGE) .
# Push the image to registry
push: build
@echo "Pushing $(FULL_IMAGE)..."
docker push $(FULL_IMAGE)
# Run locally with Docker
run:
@echo "Running $(FULL_IMAGE) locally..."
docker run -d \
-p 5800:5800 \
-e GITHUB_REPO="${GITHUB_REPO}" \
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
-e VNC_PASSWORD="${VNC_PASSWORD}" \
-v $(PWD)/home:/home \
-v $(PWD)/workspace:/workspace \
--name devcontainer \
$(FULL_IMAGE)
@echo "Access at http://localhost:5800"
# Stop the running container
stop:
@echo "Stopping devcontainer..."
docker stop devcontainer || true
docker rm devcontainer || true
# Clean up local volumes
clean: stop
@echo "Cleaning up..."
rm -rf ./home ./workspace
# Helm deployment
RELEASE_NAME ?= mydev
NAMESPACE ?= default
helm-deploy:
@echo "Deploying with Helm (release: $(RELEASE_NAME))..."
@if [ -z "$(GITHUB_REPO)" ]; then \
echo "ERROR: GITHUB_REPO environment variable is required"; \
echo "Usage: GITHUB_REPO=https://github.com/user/repo make helm-deploy"; \
exit 1; \
fi
helm upgrade --install $(RELEASE_NAME) ./chart \
--namespace $(NAMESPACE) \
--set name=$(RELEASE_NAME) \
--set githubRepo="$(GITHUB_REPO)" \
--set image.repository=$(REGISTRY)/$(IMAGE_NAME) \
--set image.tag=$(IMAGE_TAG)
helm-delete:
@echo "Deleting Helm release $(RELEASE_NAME)..."
helm uninstall $(RELEASE_NAME) --namespace $(NAMESPACE)
@echo "Note: PVC persists. To delete: kubectl delete pvc userhome-$(RELEASE_NAME) -n $(NAMESPACE)"
helm-logs:
@echo "Showing logs for $(RELEASE_NAME)..."
kubectl logs -f deployment/devcontainer-$(RELEASE_NAME) -n $(NAMESPACE)
helm-shell:
@echo "Opening shell in $(RELEASE_NAME)..."
kubectl exec -it deployment/devcontainer-$(RELEASE_NAME) -n $(NAMESPACE) -- bash
helm-port-forward:
@echo "Port forwarding $(RELEASE_NAME) to localhost:5800..."
kubectl port-forward deployment/devcontainer-$(RELEASE_NAME) 5800:5800 -n $(NAMESPACE)
# Show help
help:
@echo "Dev Container Makefile"
@echo ""
@echo "Usage: make [target]"
@echo ""
@echo "Docker Targets:"
@echo " build - Build the Docker image"
@echo " push - Push image to registry"
@echo " run - Run container locally (requires env vars)"
@echo " stop - Stop running container"
@echo " clean - Clean up containers and volumes"
@echo ""
@echo "Helm/Kubernetes Targets:"
@echo " helm-deploy - Deploy with Helm chart (requires GITHUB_REPO)"
@echo " helm-delete - Delete Helm release"
@echo " helm-logs - Show container logs"
@echo " helm-shell - Open shell in container"
@echo " helm-port-forward - Port forward to localhost"
@echo ""
@echo "Variables:"
@echo " REGISTRY - Docker registry (default: ghcr.io/cpfarhood)"
@echo " IMAGE_NAME - Image name (default: devcontainer)"
@echo " IMAGE_TAG - Image tag (default: latest)"
@echo " RELEASE_NAME - Helm release name (default: mydev)"
@echo " NAMESPACE - Kubernetes namespace (default: default)"
@echo " GITHUB_REPO - GitHub repository URL (required for helm-deploy)"
@echo ""
@echo "Environment Variables for 'make run':"
@echo " GITHUB_REPO - GitHub repository URL"
@echo " GITHUB_TOKEN - GitHub token (optional)"
@echo " VNC_PASSWORD - VNC password (optional)"
@echo ""
@echo "Examples:"
@echo " make build"
@echo " make push REGISTRY=ghcr.io/myuser IMAGE_TAG=v1.0"
@echo " GITHUB_REPO=https://github.com/user/repo make run"
@echo " GITHUB_REPO=https://github.com/user/repo make helm-deploy"
@echo " RELEASE_NAME=alice-dev GITHUB_REPO=https://github.com/alice/project make helm-deploy"
-440
View File
@@ -1,440 +0,0 @@
# Dev Container
![Build and Push](https://github.com/cpfarhood/devcontainer/actions/workflows/build-and-push.yaml/badge.svg)
A containerized cloud development environment with web-based GUI access, featuring:
- **VSCode or Google Antigravity** via browser-based VNC (port 5800)
- **SSH access** option (OpenSSH on port 22, additive with any IDE)
- **Claude Code**, **OpenCode**, and **Crush** AI coding agents (terminal-based)
- **Built-in web file manager** for uploading/downloading files via the VNC web interface
- **Helm CLI** included for Kubernetes chart development and deployment
- **Automatic GitHub repo cloning** on startup
- **Persistent home directory** via ReadWriteMany PVC
- **Kubernetes-native** Helm chart deployment
## Quick Start
### Option A: Install from Helm Repo (Recommended)
```bash
# Add the Helm repository
helm repo add devcontainer https://cpfarhood.github.io/devcontainer
helm repo update
# Deploy with one command
helm install mydev devcontainer/devcontainer \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
```
### Option B: Install from Source
```bash
# Clone and customize the quickstart template
cp chart/values-quickstart.yaml my-values.yaml
# Edit my-values.yaml to set your name and repository
helm install mydev ./chart -f my-values.yaml
```
### Option C: One-Command from Source
```bash
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
```
### Option C: Full Configuration
### 1. Create a secret
The secret is picked up automatically via `envFrom`. Keys recognised:
| Key | Purpose |
|-----|---------|
| `GITHUB_TOKEN` | PAT for private repo access (`repo` scope) |
| `VNC_PASSWORD` | Password for the VNC web UI |
| `ANTHROPIC_API_KEY` | API key — alternative to browser-based Claude login |
| `SSH_AUTHORIZED_KEYS` | Public key(s) for SSH access (required when `ssh: true`) |
| `HOMEASSISTANT_URL` | Home Assistant URL (required when `mcp.sidecars.homeassistant.enabled: true`) |
| `HOMEASSISTANT_TOKEN` | Home Assistant long-lived access token (required when `mcp.sidecars.homeassistant.enabled: true`) |
| `DATABASE_URI` | PostgreSQL connection string (required when `mcp.sidecars.pgtuner.enabled: true`) |
| `PGTUNER_EXCLUDE_USERIDS` | Comma-separated PostgreSQL user OIDs to exclude from monitoring (optional) |
```bash
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=VNC_PASSWORD='changeme'
```
Or use SealedSecrets:
```bash
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=VNC_PASSWORD='changeme' \
--dry-run=client -o yaml | \
kubeseal --format=yaml | kubectl apply -f -
```
### 2. Deploy with Helm
```bash
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
```
### 3. Access
```bash
# Local port-forward
kubectl port-forward deployment/devcontainer-mydev 5800:5800
open http://localhost:5800
```
Or configure an ingress / Gateway API HTTPRoute pointing at port 5800.
### 4. Authenticate Claude
On first launch, open a terminal in the VSCode GUI and run:
```bash
claude
```
A Chrome browser window will open inside VNC for the Claude Max OAuth login. Credentials are stored on the home PVC and persist across pod restarts.
---
## Helm Chart Reference
The Helm chart uses a logical organization with these main sections:
- **Basic Configuration**: name, image, githubRepo
- **Access & Interface**: IDE, SSH, display, user settings
- **Infrastructure**: storage, resources, cluster access
- **Integrations**: MCP sidecars
- **Smart Defaults**: auto-detection and profiles
📖 **Documentation**:
- [USAGE.md](chart/USAGE.md) - Comprehensive examples and scenarios
- [values-quickstart.yaml](chart/values-quickstart.yaml) - Minimal configuration
- [values.schema.json](chart/values.schema.json) - IDE validation support
### Core values
| Value | Default | Description |
|-------|---------|-------------|
| `name` | `""` | Instance name — used in all resource names (`devcontainer-{name}`) |
| `githubRepo` | `""` | Repository to clone into `/workspace` on startup |
| `ide.type` | `vscode` | IDE to launch — `vscode`, `antigravity`, or `none` (see below) |
| `ssh.enabled` | `false` | Also start an OpenSSH server on port 22 (additive, any IDE) |
| `fileManager.enabled` | `false` | Enable the built-in web file manager for upload/download |
| `image.repository` | `ghcr.io/cpfarhood/devcontainer` | Container image |
| `image.tag` | `latest` | Image tag |
### IDE choice
`ide.type` controls what GUI is launched in the VNC session:
| Value | Port | Description |
|-------|------|-------------|
| `vscode` (default) | 5800 (VNC) | VSCode desktop via browser-based VNC |
| `antigravity` | 5800 (VNC) | Google Antigravity (VSCode fork with AI) via VNC |
| `none` | — | No IDE; container stays alive (useful when `ssh: true`) |
### SSH access
`ssh.enabled: true` starts OpenSSH on port 22 **in addition to** the IDE. It works with any `ide.type` value:
```bash
# SSH-only (no VNC)
helm install mydev ./chart --set name=mydev --set ide.type=none --set ssh.enabled=true
# VSCode in VNC + SSH access at the same time
helm install mydev ./chart --set name=mydev --set ssh.enabled=true
```
Add your public key to the env secret:
```bash
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...'
```
Then connect:
```bash
kubectl port-forward deployment/devcontainer-mydev 2222:22
ssh -p 2222 user@localhost
```
### Web file manager
The base image includes a built-in web file manager for uploading and downloading files through the VNC web interface (port 5800). No additional sidecar is needed.
| Value | Default | Description |
|-------|---------|-------------|
| `fileManager.enabled` | `false` | Enable the web file manager |
| `fileManager.allowedPaths` | `/workspace,/config` | Paths accessible by the file manager (`AUTO`, `ALL`, or comma-separated) |
| `fileManager.deniedPaths` | `""` | Paths to deny (takes precedence over allowed) |
```bash
# Enable the file manager
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set fileManager.enabled=true
```
### Kubernetes cluster access
The `clusterAccess` value provisions a ServiceAccount, Role/ClusterRole, and binding so the devcontainer pod can interact with the Kubernetes API. The default is `none` — no RBAC resources are created.
| Value | Scope | Verbs |
|-------|-------|-------|
| `none` (default) | — | no access |
| `readonlyns` | release namespace | `get`, `list`, `watch` |
| `readwritens` | release namespace | `*` |
| `readonly` | cluster-wide | `get`, `list`, `watch` |
| `readwrite` | cluster-wide | `*` |
```bash
# Give the pod read-only access to its own namespace
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set clusterAccess=readonlyns
```
With any non-`none` value, a `ServiceAccount` named `devcontainer-{name}` is created and set as the pod's `serviceAccountName`, so `kubectl` and any in-cluster API calls use it automatically.
### MCP Sidecars
The devcontainer includes MCP (Model Context Protocol) servers as sidecar containers that enable AI assistants to interact with various services:
| Sidecar | Default | Purpose |
|---------|---------|---------|
| `mcp.sidecars.kubernetes.enabled` | `true` | Kubernetes API access via MCP |
| `mcp.sidecars.flux.enabled` | `true` | Flux GitOps operations via MCP |
| `mcp.sidecars.homeassistant.enabled` | `false` | Home Assistant smart home control via MCP |
| `mcp.sidecars.pgtuner.enabled` | `false` | PostgreSQL performance tuning and analysis via MCP |
| `mcp.sidecars.playwright.enabled` | `true` | Browser automation and web testing via MCP |
**Notes:**
- GitHub MCP is accessed via the Copilot API (`https://api.githubcopilot.com/mcp/`), not as a sidecar
- Kubernetes and Flux sidecars require `clusterAccess` != `none` to be deployed (automatically disabled when no cluster access)
- Kubernetes and Flux sidecars inherit the pod's ServiceAccount RBAC permissions (controlled by `clusterAccess`)
- Home Assistant sidecar requires `HOMEASSISTANT_URL` and `HOMEASSISTANT_TOKEN` in the env secret
- PostgreSQL tuner sidecar requires `DATABASE_URI` in the env secret (PostgreSQL connection string)
- Playwright sidecar provides browser automation and web testing capabilities
**Disable MCP sidecars:**
```bash
# Disable multiple sidecars
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcp.sidecars.kubernetes.enabled=false \
--set mcp.sidecars.flux.enabled=false \
--set mcp.sidecars.playwright.enabled=false
# Or selectively disable
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcp.sidecars.flux.enabled=false # Disable only Flux MCP
```
**Enable Home Assistant MCP:**
```bash
# Create secret with Home Assistant credentials
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=HOMEASSISTANT_URL='http://homeassistant.local:8123' \
--from-literal=HOMEASSISTANT_TOKEN='your_long_lived_access_token'
# Deploy with Home Assistant MCP enabled
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcp.sidecars.homeassistant.enabled=true
```
**Enable PostgreSQL Tuner MCP:**
```bash
# Create secret with PostgreSQL connection string
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=DATABASE_URI='postgresql://user:password@postgres.example.com:5432/dbname'
# Deploy with PostgreSQL tuner MCP enabled
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo \
--set mcp.sidecars.pgtuner.enabled=true
```
**Custom MCP configuration:**
```yaml
# values.yaml override
mcp:
sidecars:
kubernetes:
enabled: true
image:
repository: quay.io/containers/kubernetes_mcp_server
tag: v0.0.57
port: 8080
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
flux:
enabled: false # Disabled in this example
homeassistant:
enabled: true
image:
repository: ghcr.io/homeassistant-ai/ha-mcp
tag: stable
port: 8087
pgtuner:
enabled: true
image:
repository: dog830228/pgtuner_mcp
tag: latest
port: 8085
playwright:
enabled: true
image:
repository: mcr.microsoft.com/playwright/mcp
tag: latest
port: 8086
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "1000m"
```
### Display and resources
| Value | Default | Description |
|-------|---------|-------------|
| `display.width` | `1920` | VNC width (px) |
| `display.height` | `1080` | VNC height (px) |
| `display.secureConnection` | `0` | Set to `1` if TLS is not terminated upstream |
| `user.id` | `1000` | UID for the app user |
| `user.groupId` | `1000` | GID for the app user |
| `storage.size` | `32Gi` | Home PVC size |
| `storage.className` | `ceph-filesystem` | StorageClass (must be ReadWriteMany) |
| `shm.sizeLimit` | `2Gi` | `/dev/shm` size (memory-backed; used by Electron apps) |
| `resources.requests.memory` | `2Gi` | |
| `resources.requests.cpu` | `1000m` | |
| `resources.limits.memory` | `8Gi` | |
| `resources.limits.cpu` | `4000m` | |
| `envSecretName` | `devcontainer-{name}-secrets-env` | Override the secret name |
---
## Architecture
### Startup flow
```
Container start
→ cont-init.d/20-fix-user-shell.sh — fix shell/home on baseimage-gui app user
→ cont-init.d/25-start-sshd.sh — start sshd if SSH=true
→ /startapp.sh (runs as app user, UID 1000)
→ init-repo.sh
→ clone / pull GITHUB_REPO into /workspace/{repo}
→ IDE=vscode: code --new-window --wait /workspace/{repo}
IDE=antigravity: antigravity --no-sandbox --user-data-dir ~/.config/antigravity ... /workspace/{repo}
IDE=none: sleep infinity
(SSH=true: sshd also running as root on port 22; host keys persisted on PVC)
```
### Storage
| Mount | Source | Persistence |
|-------|--------|-------------|
| `/config` | ReadWriteMany PVC (`userhome-{name}`) | Survives pod restarts — stores Claude credentials, dotfiles, git config |
| `/workspace` | `emptyDir` | Ephemeral — repo is re-cloned on each pod start |
---
## Troubleshooting
### Claude not authenticated
Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth:
```bash
kubectl patch secret devcontainer-mydev-secrets-env \
--type='json' \
-p='[{"op":"add","path":"/data/ANTHROPIC_API_KEY","value":"'$(echo -n "sk-ant-..." | base64)'"}]'
```
Then restart the pod to pick up the new env var.
### VNC not loading
```bash
kubectl port-forward deployment/devcontainer-mydev 5800:5800
kubectl logs deployment/devcontainer-mydev
kubectl describe pod -l app.kubernetes.io/instance=mydev
```
### Pod not picking up new image after upgrade
The chart uses `image.tag: latest`. Kubernetes won't restart the pod on a Helm upgrade unless the Deployment spec changes. Force a restart manually:
```bash
kubectl rollout restart deployment/devcontainer-mydev
```
### Repository not cloning
```bash
kubectl logs deployment/devcontainer-mydev | grep "Repository Initialization"
kubectl exec deployment/devcontainer-mydev -- env | grep GITHUB
```
---
## Local Docker run
```bash
docker run -d \
-p 5800:5800 \
-e GITHUB_REPO="https://github.com/youruser/yourrepo" \
-e GITHUB_TOKEN="ghp_..." \
-e VNC_PASSWORD="changeme" \
-v $(pwd)/home:/home \
ghcr.io/cpfarhood/devcontainer:latest
```
---
## Building
```bash
docker build -t ghcr.io/cpfarhood/devcontainer:latest .
docker push ghcr.io/cpfarhood/devcontainer:latest
```
The image is also built and pushed automatically by CI on every push to `main` and on version tags (`v*`).
---
## Credits
- Base image: [jlesage/docker-baseimage-gui](https://github.com/jlesage/docker-baseimage-gui)
- AI assistant: [Claude](https://claude.ai)
-414
View File
@@ -1,414 +0,0 @@
# Helm Chart Values Reference
Complete reference for all configurable values in the Antigravity Dev Container Helm chart.
## Core Configuration
### name
- **Type:** String
- **Default:** `""`
- **Required:** Yes
- **Description:** Instance name used to generate resource names (`devcontainer-{name}`, `userhome-{name}`)
- **Example:** `mydev`, `alice-dev`, `team-workspace`
### githubRepo
- **Type:** String
- **Default:** `""`
- **Required:** Yes
- **Description:** GitHub repository URL to clone into `/workspace`
- **Example:** `https://github.com/username/repository`
### ide
- **Type:** String
- **Default:** `vscode`
- **Options:** `vscode`, `antigravity`, `none`
- **Description:** IDE to launch inside the container
- `vscode` — VSCode via VNC browser UI on port 5800
- `antigravity` — Google Antigravity (VSCode fork) via VNC on port 5800
- `none` — No IDE; useful when `ssh: true` is the sole access method
### ssh
- **Type:** Boolean
- **Default:** `false`
- **Description:** Start an OpenSSH server on port 22 in addition to the IDE
- **Note:** Requires `SSH_AUTHORIZED_KEYS` in env secret for key-based login
## Image Configuration
### image.repository
- **Type:** String
- **Default:** `ghcr.io/cpfarhood/devcontainer`
- **Description:** Container image repository
### image.tag
- **Type:** String
- **Default:** `latest`
- **Description:** Container image tag
- **Best Practice:** Use specific version tags for production
### image.pullPolicy
- **Type:** String
- **Default:** `Always`
- **Options:** `Always`, `IfNotPresent`, `Never`
- **Description:** Image pull policy
## Display Configuration
### display.width
- **Type:** String
- **Default:** `"1920"`
- **Description:** VNC display width in pixels
### display.height
- **Type:** String
- **Default:** `"1080"`
- **Description:** VNC display height in pixels
### secureConnection
- **Type:** String
- **Default:** `"0"`
- **Options:** `"0"`, `"1"`
- **Description:** Set to `"0"` when TLS is terminated at the gateway layer
## User Configuration
### userId
- **Type:** String
- **Default:** `"1000"`
- **Description:** UID for the app user
### groupId
- **Type:** String
- **Default:** `"1000"`
- **Description:** GID for the app user
## Storage Configuration
### storage.size
- **Type:** String
- **Default:** `32Gi`
- **Description:** Size of the persistent home directory
- **Format:** Kubernetes quantity (e.g., `10Gi`, `100Gi`, `1Ti`)
### storage.className
- **Type:** String
- **Default:** `ceph-filesystem`
- **Description:** StorageClass name (must support ReadWriteMany)
- **Examples:** `ceph-filesystem`, `nfs-client`, `efs-sc`, `azurefile`
### shm.sizeLimit
- **Type:** String
- **Default:** `2Gi`
- **Description:** `/dev/shm` size (memory-backed emptyDir for Electron apps)
## Resource Limits
### resources.requests.memory
- **Type:** String
- **Default:** `2Gi`
- **Description:** Minimum memory to reserve
- **Format:** Kubernetes quantity
### resources.requests.cpu
- **Type:** String
- **Default:** `1000m`
- **Description:** Minimum CPU to reserve
- **Format:** Millicores (`1000m` = 1 CPU core)
### resources.limits.memory
- **Type:** String
- **Default:** `8Gi`
- **Description:** Maximum memory allowed
- **Format:** Kubernetes quantity
### resources.limits.cpu
- **Type:** String
- **Default:** `4000m`
- **Description:** Maximum CPU allowed
- **Format:** Millicores (`4000m` = 4 CPU cores)
## Kubernetes Access
### clusterAccess
- **Type:** String
- **Default:** `none`
- **Options:**
- `none` — No cluster access
- `readonlyns` — Read-only access to release namespace
- `readwritens` — Full access to release namespace
- `readonly` — Read-only access cluster-wide
- `readwrite` — Full access cluster-wide
- **Description:** RBAC permissions for the pod's ServiceAccount
## Secrets
### envSecretName
- **Type:** String
- **Default:** `""` (auto-generates as `devcontainer-{name}-secrets-env`)
- **Description:** Name of existing Secret containing environment variables
- **Keys Recognized:**
- `GITHUB_TOKEN` — PAT for private repo access
- `VNC_PASSWORD` — Password for VNC web UI
- `ANTHROPIC_API_KEY` — API key for Claude
- `SSH_AUTHORIZED_KEYS` — Public keys for SSH access
- `homeassistant-url` — Home Assistant base URL (e.g., http://homeassistant.local:8123)
- `homeassistant-token` — Home Assistant long-lived access token
## MCP Sidecars
### mcpSidecars.kubernetes.enabled
- **Type:** Boolean
- **Default:** `true`
- **Description:** Enable Kubernetes MCP server sidecar
### mcpSidecars.kubernetes.image.repository
- **Type:** String
- **Default:** `quay.io/containers/kubernetes_mcp_server`
- **Description:** Kubernetes MCP server image
### mcpSidecars.kubernetes.image.tag
- **Type:** String
- **Default:** `latest`
- **Description:** Kubernetes MCP server image tag
### mcpSidecars.kubernetes.port
- **Type:** Integer
- **Default:** `8080`
- **Description:** Port for Kubernetes MCP server
### mcpSidecars.kubernetes.resources
- **Type:** Object
- **Default:**
```yaml
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
```
- **Description:** Resource limits for Kubernetes MCP sidecar
### mcpSidecars.flux.enabled
- **Type:** Boolean
- **Default:** `true`
- **Description:** Enable Flux MCP server sidecar
### mcpSidecars.flux.image.repository
- **Type:** String
- **Default:** `ghcr.io/controlplaneio-fluxcd/flux-operator-mcp`
- **Description:** Flux MCP server image
### mcpSidecars.flux.image.tag
- **Type:** String
- **Default:** `v0.41.1`
- **Description:** Flux MCP server image tag
### mcpSidecars.flux.port
- **Type:** Integer
- **Default:** `8081`
- **Description:** Port for Flux MCP server
### mcpSidecars.flux.resources
- **Type:** Object
- **Default:**
```yaml
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
```
- **Description:** Resource limits for Flux MCP sidecar
### mcpSidecars.homeassistant.enabled
- **Type:** Boolean
- **Default:** `false`
- **Description:** Enable Home Assistant MCP server sidecar
- **Note:** Requires `homeassistant-url` and `homeassistant-token` in env secret
### mcpSidecars.homeassistant.image.repository
- **Type:** String
- **Default:** `ghcr.io/homeassistant-ai/ha-mcp`
- **Description:** Home Assistant MCP server image
### mcpSidecars.homeassistant.image.tag
- **Type:** String
- **Default:** `stable`
- **Description:** Home Assistant MCP server image tag
- **Options:** `stable` (recommended), `latest` (dev builds), `v{version}` (specific version)
### mcpSidecars.homeassistant.port
- **Type:** Integer
- **Default:** `8087`
- **Description:** Port for Home Assistant MCP server (SSE mode)
### mcpSidecars.homeassistant.resources
- **Type:** Object
- **Default:**
```yaml
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
```
- **Description:** Resource limits for Home Assistant MCP sidecar
## Usage Examples
### Minimal Configuration
```yaml
name: mydev
githubRepo: https://github.com/user/repo
```
### Production Configuration
```yaml
name: prod-workspace
githubRepo: https://github.com/company/application
ide: vscode
ssh: true
image:
tag: v1.0.0
storage:
size: 100Gi
className: ceph-filesystem
resources:
requests:
memory: "4Gi"
cpu: "2000m"
limits:
memory: "16Gi"
cpu: "8000m"
clusterAccess: readwritens
mcpSidecars:
kubernetes:
enabled: true
flux:
enabled: false
```
### Development Team Configuration
```yaml
name: team-dev
githubRepo: https://github.com/team/project
ide: antigravity
display:
width: "2560"
height: "1440"
storage:
size: 50Gi
className: nfs-client
clusterAccess: readonly
```
### Smart Home Development Configuration
```yaml
name: smarthome-dev
githubRepo: https://github.com/user/home-automation
ide: vscode
clusterAccess: readwritens
mcpSidecars:
kubernetes:
enabled: true
flux:
enabled: false
homeassistant:
enabled: true
image:
tag: stable
# Requires secrets:
# homeassistant-url: http://homeassistant.local:8123
# homeassistant-token: <long-lived-access-token>
```
## Helm CLI Examples
### Using --set Flags
```bash
# Basic deployment
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/user/repo
# With multiple values
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/user/repo \
--set ide=antigravity \
--set storage.size=50Gi \
--set clusterAccess=readwritens \
--set mcpSidecars.flux.enabled=false
```
### Using Values File
Create `custom-values.yaml`:
```yaml
name: mydev
githubRepo: https://github.com/user/repo
storage:
size: 50Gi
clusterAccess: readwritens
```
Deploy:
```bash
helm install mydev ./chart -f custom-values.yaml
```
### Combining Methods
```bash
helm install mydev ./chart \
-f base-values.yaml \
-f prod-values.yaml \
--set githubRepo=https://github.com/user/repo \
--set image.tag=v2.0.0
```
## Value Precedence
Values are applied in order of precedence (highest to lowest):
1. `--set` flags on command line
2. `-f` values files (later files override earlier)
3. `chart/values.yaml` defaults
## Environment Variables
These environment variables are set in the container based on chart values:
| Environment Variable | Source Value | Description |
|---------------------|--------------|-------------|
| `GITHUB_REPO` | `githubRepo` | Repository to clone |
| `GITHUB_TOKEN` | Secret: `github-token` | PAT for private repos |
| `VNC_PASSWORD` | Secret: `vnc-password` | VNC access password |
| `ANTHROPIC_API_KEY` | Secret: `anthropic-api-key` | Claude API key |
| `SSH_AUTHORIZED_KEYS` | Secret: `ssh-authorized-keys` | SSH public keys |
| `DISPLAY_WIDTH` | `display.width` | VNC width |
| `DISPLAY_HEIGHT` | `display.height` | VNC height |
| `SECURE_CONNECTION` | `secureConnection` | TLS termination |
| `USER_ID` | `userId` | App user UID |
| `GROUP_ID` | `groupId` | App user GID |
| `IDE` | `ide` | IDE to launch |
| `SSH` | `ssh` | SSH server enabled |
-13
View File
@@ -1,13 +0,0 @@
apiVersion: v2
name: devcontainer
description: Dev Container with AI coding agents and MCP sidecars - supports persistent and dynamic deployment modes
type: application
version: 2.2.3
appVersion: "latest"
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
-381
View File
@@ -1,381 +0,0 @@
# Dev Container Helm Chart Usage Guide
This guide provides common usage patterns and examples for the Dev Container Helm chart.
## Quick Start
### 1. Minimal Installation (Recommended)
Use the quickstart values for the simplest setup:
```bash
# Copy and customize quickstart values
cp values-quickstart.yaml my-values.yaml
# Edit my-values.yaml to set your name and repo:
# name: myproject
# githubRepo: https://github.com/youruser/yourproject
# Install
helm install myproject ./chart -f my-values.yaml
```
### 2. One-Command Installation
```bash
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
```
## Common Use Cases
### Development Environment
**Scenario**: Standard development with GitHub integration
```yaml
name: dev-environment
githubRepo: https://github.com/company/project
ide:
type: vscode
mcp:
sidecars:
kubernetes:
enabled: true
playwright:
enabled: true
flux:
enabled: false # Disable if not using Flux
```
### Team Workspace
**Scenario**: Shared development environment with more resources
```yaml
name: team-workspace
githubRepo: https://github.com/company/project
resources:
requests:
memory: "4Gi"
cpu: "2000m"
limits:
memory: "16Gi"
cpu: "8000m"
storage:
size: 64Gi
ssh:
enabled: true # Enable SSH access for team
clusterAccess: readwrite # Full cluster access
```
### Kubernetes Admin Environment
**Scenario**: Platform engineering with full cluster access
```yaml
name: k8s-admin
githubRepo: https://github.com/company/k8s-configs
clusterAccess: readwrite
mcp:
sidecars:
kubernetes:
enabled: true
flux:
enabled: true
pgtuner:
enabled: true # Database administration
playwright:
enabled: false # Save resources
```
### AI/ML Development
**Scenario**: AI development with browser automation
```yaml
name: ai-playground
githubRepo: https://github.com/company/ai-project
resources:
requests:
memory: "8Gi" # More memory for ML workloads
cpu: "4000m"
limits:
memory: "32Gi"
cpu: "16000m"
storage:
size: 128Gi # Large datasets
mcp:
sidecars:
playwright:
enabled: true # Web scraping, testing
kubernetes:
enabled: false # Save resources
flux:
enabled: false
```
### Lightweight Environment
**Scenario**: Resource-constrained setup
```yaml
name: lightweight
githubRepo: https://github.com/youruser/small-project
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
storage:
size: 8Gi
mcp:
sidecars:
kubernetes:
enabled: false
flux:
enabled: false
playwright:
enabled: false
# Only keep essential sidecars enabled
```
## Secret Configuration
### Basic Secrets
```bash
# GitHub access only
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=VNC_PASSWORD='changeme'
```
### Extended Secrets
```bash
# Full feature set
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=VNC_PASSWORD='changeme' \
--from-literal=SSH_AUTHORIZED_KEYS='ssh-ed25519 AAAA...' \
--from-literal=HOMEASSISTANT_URL='http://homeassistant.local:8123' \
--from-literal=HOMEASSISTANT_TOKEN='eyJ...' \
--from-literal=DATABASE_URI='postgresql://user:pass@postgres:5432/db'
```
## Storage Configuration
### Different Storage Classes
```yaml
# For different Kubernetes distributions
storage:
className: "" # Auto-detect (recommended)
# className: longhorn # Longhorn
# className: nfs-client # NFS
# className: fast-ssd # Custom fast storage
```
### Storage Sizes by Use Case
```yaml
# Small projects
storage:
size: 8Gi
# Standard development
storage:
size: 32Gi
# Large projects / datasets
storage:
size: 128Gi
# Team environments
storage:
size: 256Gi
```
## Access Patterns
### VNC Only (Default)
```yaml
ide:
type: vscode
# Access via: kubectl port-forward deployment/devcontainer-mydev 5800:5800
```
### SSH Only
```yaml
ide:
type: none
ssh:
enabled: true
# Access via: kubectl port-forward deployment/devcontainer-mydev 2222:22
# ssh -p 2222 user@localhost
```
### Both VNC and SSH
```yaml
ide:
type: vscode
ssh:
enabled: true
# VNC: kubectl port-forward deployment/devcontainer-mydev 5800:5800
# SSH: kubectl port-forward deployment/devcontainer-mydev 2222:22
```
## Resource Profiles
### Small (1-2 developers)
```yaml
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
```
### Medium (standard development)
```yaml
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "8Gi"
cpu: "4000m"
```
### Large (intensive workloads)
```yaml
resources:
requests:
memory: "4Gi"
cpu: "2000m"
limits:
memory: "16Gi"
cpu: "8000m"
```
### XLarge (AI/ML, data processing)
```yaml
resources:
requests:
memory: "8Gi"
cpu: "4000m"
limits:
memory: "32Gi"
cpu: "16000m"
```
## MCP Sidecar Combinations
### Minimal (basic development)
```yaml
mcp:
sidecars:
kubernetes:
enabled: false
flux:
enabled: false
playwright:
enabled: true # Keep for web testing
```
### Standard (full-stack development)
```yaml
mcp:
sidecars:
kubernetes:
enabled: true
flux:
enabled: false
playwright:
enabled: true
```
### DevOps/Platform (infrastructure work)
```yaml
mcp:
sidecars:
kubernetes:
enabled: true
flux:
enabled: true
pgtuner:
enabled: true
playwright:
enabled: false
```
### All Features
```yaml
mcp:
sidecars:
kubernetes:
enabled: true
flux:
enabled: true
homeassistant:
enabled: true
pgtuner:
enabled: true
playwright:
enabled: true
```
## Troubleshooting
### Values Validation
Your IDE should automatically validate values.yaml against the schema. If not:
```bash
# Manual validation (if you have a JSON schema validator)
helm template ./chart -f values.yaml > /dev/null
```
### Common Issues
**Resource Limits**: Start with smaller resource requests and increase as needed.
**Storage Class**: Use `className: ""` for auto-detection.
**GitHub Access**: Ensure GITHUB_TOKEN has `repo` scope.
**MCP Sidecars**: Disable unused sidecars to save resources.
### Getting Help
1. Check the main [README.md](../README.md) for detailed documentation
2. Review [values.yaml](values.yaml) for all available options
3. Use [values-quickstart.yaml](values-quickstart.yaml) as a starting point
-31
View File
@@ -1,31 +0,0 @@
Dev Container "{{ .Values.name }}" has been deployed.
{{- if ne (.Values.ide.type | default "vscode") "none" }}
Access the IDE:
kubectl port-forward deployment/{{ include "devcontainer.fullname" . }} 5800:5800 -n {{ .Release.Namespace }}
Then open: http://localhost:5800
{{- end }}
{{- if .Values.ssh.enabled }}
SSH access:
kubectl port-forward deployment/{{ include "devcontainer.fullname" . }} 2222:22 -n {{ .Release.Namespace }}
Then: ssh -p 2222 user@localhost
{{- end }}
Useful commands:
Logs: kubectl logs -f deployment/{{ include "devcontainer.fullname" . }} -n {{ .Release.Namespace }}
Shell: kubectl exec -it deployment/{{ include "devcontainer.fullname" . }} -n {{ .Release.Namespace }} -- bash
{{- if not (lookup "v1" "Secret" .Release.Namespace (include "devcontainer.envSecretName" .)) }}
Optional: Create a secret for GITHUB_TOKEN, VNC_PASSWORD, etc:
kubectl create secret generic {{ include "devcontainer.envSecretName" . }} \
--from-literal=GITHUB_TOKEN=ghp_xxx \
--from-literal=VNC_PASSWORD=changeme \
-n {{ .Release.Namespace }}
{{- end }}
Note: The PVC "{{ include "devcontainer.pvcName" . }}" is protected from deletion on helm uninstall.
To remove it manually: kubectl delete pvc {{ include "devcontainer.pvcName" . }} -n {{ .Release.Namespace }}
-111
View File
@@ -1,111 +0,0 @@
{{/*
Resource name prefix: devcontainer-{name}
*/}}
{{- define "devcontainer.fullname" -}}
{{- if not .Values.name }}
{{- fail "values.name is required and must not be empty" }}
{{- end }}
{{- printf "devcontainer-%s" .Values.name }}
{{- end }}
{{/*
PVC name: userhome-{name}
*/}}
{{- define "devcontainer.pvcName" -}}
{{- printf "userhome-%s" .Values.name }}
{{- end }}
{{/*
Secret name for env vars, default to devcontainer-{name}-secrets-env
*/}}
{{- define "devcontainer.envSecretName" -}}
{{- .Values.envSecretName | default (printf "devcontainer-%s-secrets-env" .Values.name) }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "devcontainer.labels" -}}
app: devcontainer
instance: {{ .Values.name }}
app.kubernetes.io/name: devcontainer
app.kubernetes.io/instance: {{ .Values.name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
{{- end }}
{{/*
Selector labels keep narrow since changing these requires recreating the Deployment
*/}}
{{- define "devcontainer.selectorLabels" -}}
app: devcontainer
instance: {{ .Values.name }}
{{- end }}
{{/*
Smart resource sizing based on enabled features
*/}}
{{- define "devcontainer.smartResources" -}}
{{- $baseMemory := "2Gi" }}
{{- $baseCpu := "1000m" }}
{{- $limitMemory := "8Gi" }}
{{- $limitCpu := "4000m" }}
{{/* Adjust for enabled MCP sidecars */}}
{{- if .Values.mcp.sidecars.playwright.enabled }}
{{- $baseMemory = "3Gi" }}
{{- $limitMemory = "12Gi" }}
{{- end }}
{{/* Adjust for IDE type */}}
{{- if eq .Values.ide.type "antigravity" }}
{{- $baseMemory = "4Gi" }}
{{- $limitMemory = "16Gi" }}
{{- end }}
requests:
memory: {{ .Values.resources.requests.memory | default $baseMemory | quote }}
cpu: {{ .Values.resources.requests.cpu | default $baseCpu | quote }}
limits:
memory: {{ .Values.resources.limits.memory | default $limitMemory | quote }}
cpu: {{ .Values.resources.limits.cpu | default $limitCpu | quote }}
{{- end }}
{{/*
Auto-detect environment type and set smart defaults
*/}}
{{- define "devcontainer.smartDefaults" -}}
{{- $isDev := or (contains "dev" .Values.name) (contains "test" .Values.name) (contains "local" .Values.name) }}
{{- $isProd := or (contains "prod" .Values.name) (contains "production" .Values.name) }}
{{- $isTeam := or (contains "team" .Values.name) (contains "shared" .Values.name) }}
{{/* Development environment - enable more sidecars, smaller resources */}}
{{- if $isDev }}
development: true
{{/* Production environment - conservative defaults, fewer sidecars */}}
{{- else if $isProd }}
production: true
{{/* Team environment - enable SSH, more resources */}}
{{- else if $isTeam }}
team: true
{{- end }}
{{- end }}
{{/*
Smart MCP sidecar selection based on cluster access
*/}}
{{- define "devcontainer.mcpDefaults" -}}
{{- if eq .Values.clusterAccess "none" }}
{{/* No cluster access - disable k8s/flux sidecars */}}
kubernetes:
enabled: false
flux:
enabled: false
{{- else }}
{{/* Has cluster access - enable k8s sidecars */}}
kubernetes:
enabled: true
flux:
enabled: {{ ne .Values.clusterAccess "readonly" }}
{{- end }}
{{- end }}
-308
View File
@@ -1,308 +0,0 @@
{{- if eq .Values.deploymentMode "persistent" }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "devcontainer.fullname" . }}
labels:
{{- include "devcontainer.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "devcontainer.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "devcontainer.labels" . | nindent 8 }}
spec:
{{- if ne (.Values.clusterAccess | default "none") "none" }}
serviceAccountName: {{ include "devcontainer.fullname" . }}
{{- end }}
securityContext:
fsGroup: 1000
fsGroupChangePolicy: "OnRootMismatch"
{{- if and .Values.ide.type (eq .Values.ide.type "antigravity") }}
initContainers:
- name: setup-userdata
image: busybox:1.37
command: ['sh', '-c']
args:
- |
echo "Setting up userdata directory..."
mkdir -p /config/userdata
chown 1000:1000 /config/userdata
chmod 755 /config/userdata
echo "Userdata directory setup complete"
volumeMounts:
- name: userhome
mountPath: /config
securityContext:
runAsUser: 0
runAsGroup: 0
{{- end }}
containers:
- name: devcontainer
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
{{- if ne (.Values.ide.type | default "vscode") "none" }}
- containerPort: 5800
name: vnc-web
protocol: TCP
{{- end }}
{{- if .Values.ssh.enabled }}
- containerPort: 22
name: ssh
protocol: TCP
{{- end }}
env:
- name: IDE
value: {{ .Values.ide.type | default "vscode" | quote }}
- name: SSH
value: {{ .Values.ssh.enabled | toString | quote }}
- name: USER_ID
value: {{ .Values.user.id | quote }}
- name: GROUP_ID
value: {{ .Values.user.groupId | quote }}
- name: DISPLAY_WIDTH
value: {{ .Values.display.width | quote }}
- name: DISPLAY_HEIGHT
value: {{ .Values.display.height | quote }}
- name: SECURE_CONNECTION
value: {{ .Values.display.secureConnection | quote }}
{{- if .Values.fileManager.enabled }}
- name: WEB_FILE_MANAGER
value: "1"
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
value: {{ .Values.fileManager.allowedPaths | quote }}
{{- if .Values.fileManager.deniedPaths }}
- name: WEB_FILE_MANAGER_DENIED_PATHS
value: {{ .Values.fileManager.deniedPaths | quote }}
{{- end }}
{{- end }}
{{- if .Values.githubRepo }}
- name: GITHUB_REPO
value: {{ .Values.githubRepo | quote }}
{{- end }}
envFrom:
- secretRef:
name: {{ include "devcontainer.envSecretName" . }}
optional: true
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: userhome
mountPath: /config
- name: workspace
mountPath: /workspace
- name: shm
mountPath: /dev/shm
{{- if ne (.Values.ide.type | default "vscode") "none" }}
livenessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 10
periodSeconds: 5
{{- else if .Values.ssh.enabled }}
livenessProbe:
tcpSocket:
port: 22
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
tcpSocket:
port: 22
initialDelaySeconds: 5
periodSeconds: 5
{{- end }}
{{- if and .Values.mcp.sidecars.kubernetes.enabled (ne .Values.clusterAccess "none") }}
- name: kubernetes-mcp
image: "{{ .Values.mcp.sidecars.kubernetes.image.repository }}:{{ .Values.mcp.sidecars.kubernetes.image.tag }}"
args:
- --port
- {{ .Values.mcp.sidecars.kubernetes.port | quote }}
ports:
- containerPort: {{ .Values.mcp.sidecars.kubernetes.port }}
name: k8s-mcp
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: {{ .Values.mcp.sidecars.kubernetes.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: {{ .Values.mcp.sidecars.kubernetes.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.kubernetes.resources | nindent 12 }}
{{- end }}
{{- if and .Values.mcp.sidecars.flux.enabled (ne .Values.clusterAccess "none") }}
- name: flux-mcp
image: "{{ .Values.mcp.sidecars.flux.image.repository }}:{{ .Values.mcp.sidecars.flux.image.tag }}"
args:
- serve
- --transport=sse
- --port={{ .Values.mcp.sidecars.flux.port }}
ports:
- containerPort: {{ .Values.mcp.sidecars.flux.port }}
name: flux-mcp
protocol: TCP
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.flux.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.flux.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.flux.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcp.sidecars.helm.enabled }}
- name: helm-mcp
image: "{{ .Values.mcp.sidecars.helm.image.repository }}:{{ .Values.mcp.sidecars.helm.image.tag }}"
args:
- -mode=sse
ports:
- containerPort: {{ .Values.mcp.sidecars.helm.port }}
name: helm-mcp
protocol: TCP
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.helm.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.helm.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.helm.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcp.sidecars.homeassistant.enabled }}
- name: homeassistant-mcp
image: "{{ .Values.mcp.sidecars.homeassistant.image.repository }}:{{ .Values.mcp.sidecars.homeassistant.image.tag }}"
imagePullPolicy: IfNotPresent
command: ["fastmcp", "run", "--transport", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.homeassistant.port }}"]
ports:
- name: homeassistant
containerPort: {{ .Values.mcp.sidecars.homeassistant.port }}
env:
- name: HOMEASSISTANT_URL
valueFrom:
secretKeyRef:
name: {{ include "devcontainer.envSecretName" . }}
key: HOMEASSISTANT_URL
optional: true
- name: HOMEASSISTANT_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "devcontainer.envSecretName" . }}
key: HOMEASSISTANT_TOKEN
optional: true
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.homeassistant.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.homeassistant.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.homeassistant.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcp.sidecars.pgtuner.enabled }}
- name: pgtuner-mcp
image: "{{ .Values.mcp.sidecars.pgtuner.image.repository }}:{{ .Values.mcp.sidecars.pgtuner.image.tag }}"
imagePullPolicy: Always # pgtuner uses `latest` tag (no versioned releases available)
command: ["python", "-m", "pgtuner_mcp", "--mode", "sse", "--host", "0.0.0.0", "--port", "{{ .Values.mcp.sidecars.pgtuner.port }}"]
ports:
- name: pgtuner
containerPort: {{ .Values.mcp.sidecars.pgtuner.port }}
env:
- name: DATABASE_URI
valueFrom:
secretKeyRef:
name: {{ include "devcontainer.envSecretName" . }}
key: DATABASE_URI
optional: true
- name: PGTUNER_EXCLUDE_USERIDS
valueFrom:
secretKeyRef:
name: {{ include "devcontainer.envSecretName" . }}
key: PGTUNER_EXCLUDE_USERIDS
optional: true
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.pgtuner.port }}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.pgtuner.port }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.pgtuner.resources | nindent 12 }}
{{- end }}
{{- if .Values.mcp.sidecars.playwright.enabled }}
- name: playwright-mcp
image: "{{ .Values.mcp.sidecars.playwright.image.repository }}:{{ .Values.mcp.sidecars.playwright.image.tag }}"
imagePullPolicy: IfNotPresent
command: ["node"]
args:
- cli.js
- --headless
- --browser
- chromium
- --no-sandbox
- --host
- 0.0.0.0
- --port
- {{ .Values.mcp.sidecars.playwright.port | quote }}
ports:
- name: playwright
containerPort: {{ .Values.mcp.sidecars.playwright.port }}
livenessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.playwright.port }}
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
tcpSocket:
port: {{ .Values.mcp.sidecars.playwright.port }}
initialDelaySeconds: 10
periodSeconds: 5
resources:
{{- toYaml .Values.mcp.sidecars.playwright.resources | nindent 12 }}
securityContext:
runAsUser: 1000
runAsGroup: 1000
{{- end }}
volumes:
- name: workspace
emptyDir: {}
- name: shm
emptyDir:
medium: Memory
sizeLimit: {{ .Values.shm.sizeLimit }}
- name: userhome
persistentVolumeClaim:
claimName: {{ include "devcontainer.pvcName" . }}
{{- end }}
-68
View File
@@ -1,68 +0,0 @@
{{- if and (eq .Values.deploymentMode "dynamic") .Values.dynamic.ingress.enabled .Values.dynamic.ingress.host }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "devcontainer.fullname" . }}-dynamic
labels:
{{- include "devcontainer.labels" . | nindent 4 }}
app.kubernetes.io/component: dynamic-ingress
annotations:
{{- if .Values.dynamic.ingress.className }}
kubernetes.io/ingress.class: {{ .Values.dynamic.ingress.className }}
{{- end }}
# SSL configuration
{{- if .Values.dynamic.ingress.tls.enabled }}
cert-manager.io/cluster-issuer: {{ .Values.dynamic.ingress.tls.issuer | quote }}
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
{{- end }}
# Authentik forward auth (if enabled)
{{- if .Values.dynamic.ingress.authentik.enabled }}
nginx.ingress.kubernetes.io/auth-url: {{ .Values.dynamic.ingress.authentik.authUrl | quote }}
nginx.ingress.kubernetes.io/auth-signin: {{ .Values.dynamic.ingress.authentik.signIn | quote }}
nginx.ingress.kubernetes.io/auth-response-headers: "X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name"
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Host $http_host;
{{- end }}
# WebSocket support for VNC connections
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# Large file upload support (for file manager)
nginx.ingress.kubernetes.io/client-max-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
# Custom server snippet for GitHub repo logging
nginx.ingress.kubernetes.io/server-snippet: |
location ~ ^/github/([^/]+/[^/]+) {
# Log the GitHub repo being accessed
access_log /var/log/nginx/devcontainer-access.log combined;
# Set additional headers for audit/monitoring
proxy_set_header X-GitHub-Repo-Requested https://github.com/$1;
proxy_set_header X-Request-Timestamp $time_iso8601;
proxy_set_header X-Client-IP $remote_addr;
}
spec:
{{- if .Values.dynamic.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.dynamic.ingress.host }}
secretName: {{ .Values.dynamic.ingress.tls.secretName | default (printf "%s-tls" (include "devcontainer.fullname" .)) }}
{{- end }}
rules:
- host: {{ .Values.dynamic.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "devcontainer.fullname" . }}-routing-proxy
port:
number: 80
{{- end }}
-98
View File
@@ -1,98 +0,0 @@
{{- if eq .Values.deploymentMode "dynamic" }}
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: {{ include "devcontainer.fullname" . }}
labels:
{{- include "devcontainer.labels" . | nindent 4 }}
annotations:
# Knative scaling annotations
autoscaling.knative.dev/minScale: {{ .Values.dynamic.knative.minScale | quote }}
autoscaling.knative.dev/maxScale: {{ .Values.dynamic.knative.maxScale | quote }}
autoscaling.knative.dev/target: {{ .Values.dynamic.knative.target | quote }}
autoscaling.knative.dev/scale-to-zero-grace-period: {{ .Values.dynamic.knative.scaleToZeroGracePeriod | quote }}
spec:
template:
metadata:
labels:
{{- include "devcontainer.labels" . | nindent 8 }}
annotations:
# Container configuration
autoscaling.knative.dev/targetPort: "5800"
serving.knative.dev/timeoutSeconds: {{ .Values.dynamic.knative.timeoutSeconds | quote }}
# Scaling configuration
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
autoscaling.knative.dev/metric: "concurrency"
spec:
# Container startup timeout
timeoutSeconds: {{ .Values.dynamic.knative.timeoutSeconds }}
containers:
- name: devcontainer
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 5800
name: vnc-web
env:
# Dynamic mode flags
- name: SERVERLESS_MODE
value: "true"
- name: DYNAMIC_GITHUB_ROUTING
value: "true"
- name: DEPLOYMENT_MODE
value: "dynamic"
# Standard configuration
- name: IDE
value: {{ .Values.ide.type | default "vscode" | quote }}
- name: USER_ID
value: {{ .Values.user.id | quote }}
- name: GROUP_ID
value: {{ .Values.user.groupId | quote }}
- name: DISPLAY_WIDTH
value: {{ .Values.display.width | quote }}
- name: DISPLAY_HEIGHT
value: {{ .Values.display.height | quote }}
- name: SECURE_CONNECTION
value: {{ .Values.display.secureConnection | quote }}
# File manager (always enabled in dynamic mode for easy file transfer)
- name: WEB_FILE_MANAGER
value: "1"
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
value: "/workspace,/tmp" # No persistent /config in dynamic mode
# Secret environment variables
envFrom:
- secretRef:
name: {{ include "devcontainer.envSecretName" . }}
optional: true
resources:
{{- toYaml .Values.dynamic.knative.resources | nindent 10 }}
volumeMounts:
- name: tmp-home
mountPath: /config
- name: shm
mountPath: /dev/shm
# Health probes (adjusted for dynamic mode startup time)
readinessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
livenessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
volumes:
- name: tmp-home
emptyDir: {} # Ephemeral - each instance gets fresh home
- name: shm
emptyDir:
medium: Memory
sizeLimit: {{ .Values.shm.sizeLimit }}
{{- end }}
-19
View File
@@ -1,19 +0,0 @@
{{- if eq .Values.deploymentMode "persistent" }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "devcontainer.pvcName" . }}
annotations:
helm.sh/resource-policy: keep
labels:
{{- include "devcontainer.labels" . | nindent 4 }}
spec:
accessModes:
- ReadWriteMany
{{- if .Values.storage.className }}
storageClassName: {{ .Values.storage.className }}
{{- end }}
resources:
requests:
storage: {{ .Values.storage.size }}
{{- end }}
-99
View File
@@ -1,99 +0,0 @@
{{- if eq .Values.deploymentMode "persistent" }}
{{- $access := .Values.clusterAccess | default "none" }}
{{- $name := include "devcontainer.fullname" . }}
{{- $ns := .Release.Namespace }}
{{- $labels := include "devcontainer.labels" . }}
{{- if ne $access "none" }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ $name }}
namespace: {{ $ns }}
labels:
{{- $labels | nindent 4 }}
{{- if or (eq $access "readonlyns") (eq $access "readwritens") }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ $name }}
namespace: {{ $ns }}
labels:
{{- $labels | nindent 4 }}
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs:
{{- if eq $access "readonlyns" }}
- get
- list
- watch
{{- else }}
- "*"
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ $name }}
namespace: {{ $ns }}
labels:
{{- $labels | nindent 4 }}
subjects:
- kind: ServiceAccount
name: {{ $name }}
namespace: {{ $ns }}
roleRef:
kind: Role
name: {{ $name }}
apiGroup: rbac.authorization.k8s.io
{{- end }}
{{- if or (eq $access "readonly") (eq $access "readwrite") }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ $name }}
labels:
{{- $labels | nindent 4 }}
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs:
{{- if eq $access "readonly" }}
- get
- list
- watch
{{- else }}
- "*"
{{- end }}
- nonResourceURLs: ["*"]
verbs:
{{- if eq $access "readonly" }}
- get
{{- else }}
- "*"
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ $name }}
labels:
{{- $labels | nindent 4 }}
subjects:
- kind: ServiceAccount
name: {{ $name }}
namespace: {{ $ns }}
roleRef:
kind: ClusterRole
name: {{ $name }}
apiGroup: rbac.authorization.k8s.io
{{- end }}
{{- end }}
{{- end }}
-66
View File
@@ -1,66 +0,0 @@
{{- if and (eq .Values.deploymentMode "dynamic") .Values.dynamic.routingProxy.enabled }}
---
# Routing proxy deployment for dynamic GitHub repo extraction
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "devcontainer.fullname" . }}-routing-proxy
labels:
{{- include "devcontainer.labels" . | nindent 4 }}
app.kubernetes.io/component: routing-proxy
spec:
replicas: {{ .Values.dynamic.routingProxy.replicas }}
selector:
matchLabels:
{{- include "devcontainer.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: routing-proxy
template:
metadata:
labels:
{{- include "devcontainer.labels" . | nindent 8 }}
app.kubernetes.io/component: routing-proxy
spec:
containers:
- name: routing-proxy
image: "{{ .Values.dynamic.routingProxy.image.repository }}:{{ .Values.dynamic.routingProxy.image.tag }}"
imagePullPolicy: {{ .Values.dynamic.routingProxy.image.pullPolicy }}
ports:
- containerPort: 8080
name: http
env:
- name: DEVCONTAINER_SERVICE_URL
value: "{{ include "devcontainer.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local"
resources:
{{- toYaml .Values.dynamic.routingProxy.resources | nindent 10 }}
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
---
# Service for routing proxy
apiVersion: v1
kind: Service
metadata:
name: {{ include "devcontainer.fullname" . }}-routing-proxy
labels:
{{- include "devcontainer.labels" . | nindent 4 }}
app.kubernetes.io/component: routing-proxy
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
name: http
selector:
{{- include "devcontainer.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: routing-proxy
{{- end }}
-24
View File
@@ -1,24 +0,0 @@
{{- if eq .Values.deploymentMode "persistent" }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "devcontainer.fullname" . }}
labels:
{{- include "devcontainer.labels" . | nindent 4 }}
spec:
ports:
{{- if ne (.Values.ide.type | default "vscode") "none" }}
- port: 5800
name: vnc-web
protocol: TCP
targetPort: vnc-web
{{- end }}
{{- if .Values.ssh.enabled }}
- port: 22
name: ssh
protocol: TCP
targetPort: ssh
{{- end }}
selector:
{{- include "devcontainer.labels" . | nindent 4 }}
{{- end }}
-115
View File
@@ -1,115 +0,0 @@
# Example values for dynamic (serverless) deployment mode
# Copy this file and customize for your environment:
# cp values-dynamic.yaml my-dynamic-values.yaml
# =============================================================================
# BASIC CONFIGURATION
# =============================================================================
name: "mydev" # REQUIRED: Instance name
deploymentMode: dynamic # Use serverless/dynamic mode
# Container images
image:
repository: ghcr.io/cpfarhood/devcontainer
tag: "2.0.0-dev"
pullPolicy: Always
# githubRepo is ignored in dynamic mode - repos are specified via URL routing
# =============================================================================
# ACCESS & INTERFACE
# =============================================================================
ide:
type: vscode # vscode | antigravity | none
# SSH not supported in dynamic mode (ephemeral containers)
ssh:
enabled: false
# File manager automatically enabled in dynamic mode for file transfer
fileManager:
enabled: true
# =============================================================================
# DYNAMIC MODE CONFIGURATION
# =============================================================================
dynamic:
# Knative Service auto-scaling configuration
knative:
minScale: 0 # Scale to zero when not in use
maxScale: 10 # Maximum concurrent instances
target: 1 # Requests per instance (1 = perfect isolation)
scaleToZeroGracePeriod: "5m" # Keep instances warm for 5 minutes
timeoutSeconds: 600 # 10 minutes for repo cloning + IDE startup
# Resources per container instance
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
# Routing proxy (extracts GitHub repo from URL path)
routingProxy:
enabled: true
replicas: 2 # High availability
image:
repository: ghcr.io/cpfarhood/devcontainer-routing-proxy
tag: latest
pullPolicy: Always
# Ingress configuration
ingress:
enabled: true
className: nginx
host: "devcontainer.example.com" # REQUIRED: Set your domain
# SSL with cert-manager
tls:
enabled: true
# secretName: "" # Auto-generated if empty
issuer: "letsencrypt-prod"
# Authentik forward auth (configure after Authentik setup)
authentik:
enabled: false # Set to true when ready
authUrl: "http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
signIn: "https://auth.example.com/outpost.goauthentik.io/start?rd=$escaped_request_uri"
# =============================================================================
# STANDARD CONFIGURATION (applies to both modes)
# =============================================================================
# Display settings
display:
width: "1920"
height: "1080"
secureConnection: "0"
# User configuration
user:
id: "1000"
groupId: "1000"
# Resource allocation (container shared memory)
shm:
sizeLimit: 2Gi
# MCP sidecars are not supported in dynamic mode (Knative limitation)
mcp:
sidecars:
kubernetes:
enabled: false
flux:
enabled: false
homeassistant:
enabled: false
pgtuner:
enabled: false
playwright:
enabled: false
-58
View File
@@ -1,58 +0,0 @@
# =============================================================================
# QUICKSTART VALUES - Just set these 3 essentials!
# =============================================================================
# Instance name (required)
name: mydev
# GitHub repository to clone (required)
githubRepo: https://github.com/youruser/yourrepo
# IDE choice (optional - defaults to vscode)
# Options: vscode | antigravity | none
ide:
type: vscode
# =============================================================================
# COMMON CUSTOMIZATIONS (optional)
# =============================================================================
# Enable SSH access
# ssh:
# enabled: true
# Adjust resources for smaller/larger workloads
# resources:
# requests:
# memory: "1Gi" # Smaller
# cpu: "500m"
# limits:
# memory: "4Gi" # Smaller
# cpu: "2000m"
# Different storage size
# storage:
# size: 16Gi # Smaller
# Disable some MCP sidecars to save resources
# mcp:
# sidecars:
# kubernetes:
# enabled: false
# flux:
# enabled: false
# =============================================================================
# USAGE INSTRUCTIONS
# =============================================================================
# 1. Copy this file: cp values-quickstart.yaml my-values.yaml
# 2. Edit the 'name' and 'githubRepo' fields above
# 3. Deploy: helm install mydev ./chart -f my-values.yaml
# 4. Access: kubectl port-forward deployment/devcontainer-mydev 5800:5800
# 5. Open: http://localhost:5800
# For secrets (GitHub token, passwords):
# kubectl create secret generic devcontainer-mydev-secrets-env \
# --from-literal=GITHUB_TOKEN='ghp_...' \
# --from-literal=VNC_PASSWORD='changeme'
-331
View File
@@ -1,331 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/cpfarhood/devcontainer/chart/values.schema.json",
"title": "Dev Container Helm Chart Values Schema",
"description": "Schema for validating values.yaml in the Dev Container Helm chart",
"type": "object",
"additionalProperties": true,
"properties": {
"name": {
"type": "string",
"description": "Instance name used to generate resource names",
"pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$",
"minLength": 1,
"maxLength": 63
},
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"description": "Container image repository"
},
"tag": {
"type": "string",
"description": "Container image tag"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"],
"description": "Image pull policy"
}
},
"required": ["repository", "tag"]
},
"deploymentMode": {
"type": "string",
"enum": ["persistent", "dynamic"],
"description": "Deployment mode: persistent (PVC-based) or dynamic (Knative serverless)"
},
"githubRepo": {
"type": "string",
"description": "GitHub repository URL to clone (required in persistent mode, ignored in dynamic mode)"
},
"fileManager": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Enable the built-in web file manager"
},
"allowedPaths": {
"type": "string",
"description": "Paths accessible by the file manager (AUTO, ALL, or comma-separated list)"
},
"deniedPaths": {
"type": "string",
"description": "Paths to deny access to (takes precedence over allowedPaths)"
}
},
"required": ["enabled"]
},
"dynamic": {
"type": "object",
"description": "Configuration for dynamic (serverless) deployment mode",
"properties": {
"knative": {
"type": "object",
"properties": {
"minScale": { "type": "integer", "minimum": 0 },
"maxScale": { "type": "integer", "minimum": 1 },
"target": { "type": "integer", "minimum": 1 },
"scaleToZeroGracePeriod": { "type": "string" },
"timeoutSeconds": { "type": "integer", "minimum": 60 },
"resources": {
"type": "object",
"properties": {
"requests": { "$ref": "#/$defs/resourceSpec" },
"limits": { "$ref": "#/$defs/resourceSpec" }
}
}
}
},
"routingProxy": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"replicas": { "type": "integer", "minimum": 1 },
"image": {
"type": "object",
"properties": {
"repository": { "type": "string" },
"tag": { "type": "string" },
"pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] }
}
},
"resources": {
"type": "object",
"properties": {
"requests": { "$ref": "#/$defs/resourceSpec" },
"limits": { "$ref": "#/$defs/resourceSpec" }
}
}
}
},
"ingress": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"className": { "type": "string" },
"host": { "type": "string" },
"tls": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"secretName": { "type": "string" },
"issuer": { "type": "string" }
}
},
"authentik": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"authUrl": { "type": "string" },
"signIn": { "type": "string" }
}
}
}
}
}
},
"ide": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["vscode", "antigravity", "none"],
"description": "IDE to launch in the container"
}
},
"required": ["type"]
},
"ssh": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Enable SSH server on port 22"
}
},
"required": ["enabled"]
},
"display": {
"type": "object",
"properties": {
"width": {
"type": "string",
"pattern": "^[0-9]+$",
"description": "VNC display width in pixels"
},
"height": {
"type": "string",
"pattern": "^[0-9]+$",
"description": "VNC display height in pixels"
},
"secureConnection": {
"type": "string",
"enum": ["0", "1"],
"description": "Enable secure VNC connection"
}
},
"required": ["width", "height", "secureConnection"]
},
"user": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^[0-9]+$",
"description": "User ID (UID)"
},
"groupId": {
"type": "string",
"pattern": "^[0-9]+$",
"description": "Group ID (GID)"
}
},
"required": ["id", "groupId"]
},
"storage": {
"type": "object",
"properties": {
"size": {
"type": "string",
"pattern": "^[0-9]+[KMGT]i$",
"description": "Storage size (e.g., 32Gi)"
},
"className": {
"type": "string",
"description": "Storage class name (must support ReadWriteMany)"
}
},
"required": ["size"]
},
"resources": {
"type": "object",
"properties": {
"requests": {
"$ref": "#/$defs/resourceSpec"
},
"limits": {
"$ref": "#/$defs/resourceSpec"
}
},
"required": ["requests", "limits"]
},
"shm": {
"type": "object",
"properties": {
"sizeLimit": {
"type": "string",
"pattern": "^[0-9]+[KMGT]i$",
"description": "Shared memory size limit"
}
},
"required": ["sizeLimit"]
},
"clusterAccess": {
"type": "string",
"enum": ["none", "readonlyns", "readwritens", "readonly", "readwrite"],
"description": "Kubernetes cluster access level"
},
"mcp": {
"type": "object",
"properties": {
"sidecars": {
"type": "object",
"properties": {
"kubernetes": {
"$ref": "#/$defs/mcpSidecar"
},
"flux": {
"$ref": "#/$defs/mcpSidecar"
},
"homeassistant": {
"$ref": "#/$defs/mcpSidecar"
},
"pgtuner": {
"$ref": "#/$defs/mcpSidecar"
},
"helm": {
"$ref": "#/$defs/mcpSidecar"
},
"playwright": {
"$ref": "#/$defs/mcpSidecar"
}
},
"additionalProperties": false
}
},
"required": ["sidecars"]
},
"envSecretName": {
"type": "string",
"description": "Custom environment secret name"
},
"resourceProfile": {
"type": "string",
"enum": ["auto", "small", "medium", "large", "xlarge"],
"description": "Resource profile preset"
}
},
"required": ["name"],
"$defs": {
"resourceSpec": {
"type": "object",
"properties": {
"memory": {
"type": "string",
"pattern": "^[0-9]+[KMGT]i$",
"description": "Memory resource specification"
},
"cpu": {
"type": "string",
"pattern": "^[0-9]+m?$",
"description": "CPU resource specification"
}
},
"required": ["memory", "cpu"]
},
"mcpSidecar": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"description": "Enable this MCP sidecar"
},
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"required": ["repository", "tag"]
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535,
"description": "Port for the MCP sidecar"
},
"resources": {
"type": "object",
"properties": {
"requests": {
"$ref": "#/$defs/resourceSpec"
},
"limits": {
"$ref": "#/$defs/resourceSpec"
}
},
"required": ["requests", "limits"]
}
},
"required": ["enabled", "image", "port", "resources"]
}
}
}
-261
View File
@@ -1,261 +0,0 @@
# =============================================================================
# BASIC CONFIGURATION
# =============================================================================
# Instance name — used to generate resource names (devcontainer-{name}, userhome-{name})
name: ""
# Deployment mode controls the infrastructure pattern
# - persistent: Traditional model with PVC storage, single long-lived deployment
# - dynamic: Serverless model with Knative, auto-scaling from 0, dynamic GitHub routing
deploymentMode: persistent # persistent | dynamic
# Container image configuration
image:
repository: ghcr.io/cpfarhood/devcontainer
tag: latest
pullPolicy: Always
# GitHub repository to clone into /workspace (ignored in dynamic mode - uses URL routing)
githubRepo: ""
# =============================================================================
# ACCESS & INTERFACE
# =============================================================================
# IDE configuration
ide:
# Options: vscode | antigravity | none
type: vscode
# SSH access configuration
ssh:
enabled: false
# Web file manager — built-in upload/download via the VNC web interface (port 5800)
# Uses the base image's WEB_FILE_MANAGER feature (no extra sidecar needed)
fileManager:
enabled: false
# Paths the file manager can access (default: AUTO = mapped volumes)
# Options: AUTO | ALL | comma-separated list of paths
allowedPaths: "/workspace,/config"
# Paths to deny (takes precedence over allowedPaths)
deniedPaths: ""
# VNC display settings
display:
width: "1920"
height: "1080"
secureConnection: "0" # Set to "1" when TLS is not terminated upstream
# User configuration
user:
id: "1000"
groupId: "1000"
# =============================================================================
# INFRASTRUCTURE & RESOURCES
# =============================================================================
# Storage configuration
storage:
size: 32Gi
className: "" # Empty string uses the cluster's default StorageClass (must support ReadWriteMany)
# Resource allocation
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "8Gi"
cpu: "4000m"
# Shared memory for Electron apps (Chrome, Antigravity)
shm:
sizeLimit: 2Gi
# Kubernetes cluster access via RBAC
# Options: none | readonlyns | readwritens | readonly | readwrite
clusterAccess: none
# =============================================================================
# INTEGRATIONS
# =============================================================================
# MCP (Model Context Protocol) server sidecars
mcp:
sidecars:
# Kubernetes API access
kubernetes:
enabled: true
image:
repository: quay.io/containers/kubernetes_mcp_server
tag: v0.0.57
port: 8080
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# Flux GitOps operations
flux:
enabled: true
image:
repository: ghcr.io/controlplaneio-fluxcd/flux-operator-mcp
tag: v0.41.1
port: 8081
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# Helm chart browsing and management
helm:
enabled: true
image:
repository: ghcr.io/zekker6/mcp-helm
tag: v1.3.1
port: 8012
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# Home Assistant smart home control
homeassistant:
enabled: false # Requires HOMEASSISTANT_URL and HOMEASSISTANT_TOKEN
image:
repository: ghcr.io/homeassistant-ai/ha-mcp
tag: "6.7.1"
port: 8087
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# PostgreSQL performance tuning
pgtuner:
enabled: false # Requires DATABASE_URI in secrets
image:
repository: dog830228/pgtuner_mcp
tag: latest
port: 8085
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# Browser automation and web testing
playwright:
enabled: true
image:
repository: mcr.microsoft.com/playwright/mcp
tag: v0.0.68
port: 8086
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "1000m"
# =============================================================================
# SMART DEFAULTS & AUTO-DETECTION
# =============================================================================
# Environment auto-detection based on name patterns
# Automatically adjusts defaults for dev/test/prod/team environments
autoDetect:
environment: true # Auto-detect dev/prod/team from name
storageClass: true # Auto-detect ReadWriteMany storage class
resources: true # Auto-size resources based on enabled features
# Resource profiles (auto-selected based on environment and features)
# Override specific values above to customize
resourceProfile: auto # auto | small | medium | large | xlarge
# =============================================================================
# DYNAMIC MODE CONFIGURATION (deploymentMode: dynamic)
# =============================================================================
# Dynamic mode uses Knative Services and routing proxy for serverless operation
dynamic:
# Knative Service configuration
knative:
# Scaling configuration
minScale: 0 # Scale to zero when not in use
maxScale: 10 # Maximum number of concurrent instances
target: 1 # Requests per instance (isolation = 1 request per pod)
scaleToZeroGracePeriod: "5m" # Keep instances warm for 5 minutes
# Container startup timeout (repo cloning + IDE startup)
timeoutSeconds: 600 # 10 minutes
# Resource configuration (per instance)
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
# Routing proxy configuration (extracts GitHub repo from URL)
routingProxy:
enabled: true
replicas: 2 # High availability
image:
repository: ghcr.io/cpfarhood/devcontainer-routing-proxy
tag: latest
pullPolicy: Always
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
# Ingress configuration for dynamic mode
ingress:
enabled: true
className: nginx
host: "" # Set this to your domain (e.g., devcontainer.farh.net)
# TLS configuration
tls:
enabled: true
secretName: "" # Auto-generated if empty
issuer: "letsencrypt-prod" # cert-manager ClusterIssuer
# Authentik forward auth configuration
authentik:
enabled: false # Set to true when Authentik is configured
authUrl: "http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx"
signIn: "https://auth.example.com/outpost.goauthentik.io/start?rd=$escaped_request_uri"
# =============================================================================
# ADVANCED CONFIGURATION
# =============================================================================
# Custom env secret name (defaults to: devcontainer-{name}-secrets-env)
envSecretName: ""
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head><title>Dev Container Helm Chart Repository</title></head>
<body>
<h1>Dev Container Helm Chart Repository</h1>
<p>Add this repository to Helm:</p>
<pre>helm repo add devcontainer https://farhoodliquor.github.io/devcontainer</pre>
<p>Install the chart:</p>
<pre>helm install mydev devcontainer/devcontainer --set name=mydev</pre>
</body>
</html>
+286
View File
@@ -0,0 +1,286 @@
apiVersion: v1
entries:
devcontainer:
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.634530101Z"
description: Dev Container with AI coding agents and MCP sidecars
digest: 0c46b5d7a889cc21c28b8b088eb6c0c785a3f9324a4b39d14813c4eefe3f8e7a
keywords:
- development
- devcontainer
- vscode
- ai
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.6.0.tgz
version: 2.6.0
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.633861353Z"
description: Dev Container with AI coding agents and MCP sidecars
digest: 010294a1a9b4bb6a92c685d819a0cd34dd03bc111da715d0807799168d0aba66
keywords:
- development
- devcontainer
- vscode
- ai
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.4.0.tgz
version: 2.4.0
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.633145718Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: a650e9c7a8feb961232aee4048d3bf0ff1d04c55100f51a7db138e1a0f8b524e
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.3.0.tgz
version: 2.3.0
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.631873087Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: f5c440846e7672239a6f7b14a393888988ef627d896bc967bfc018130d65921d
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.5.tgz
version: 2.2.5
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.6304485Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: 2bea7dc5c198a5b4dab0b74f0a75089210c7ba49b56176ba2af205b7dac3fe23
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.4.tgz
version: 2.2.4
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.62965028Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: afde89b22d7e4a5dfd4c918a06258d9f27f1b17493a70dba98d1ae544280505a
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.3.tgz
version: 2.2.3
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.628801186Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: 5b3b994b74da01579156021fcfb718c61989def7c16cafadb36e7ddc90cbeea7
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.2.tgz
version: 2.2.2
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.627970857Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: 8db383b24252edd37998c56bbba76793d1b6eeb37365a6894a713eef6af81210
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.1.tgz
version: 2.2.1
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.627169642Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: a1ea207bc96a35cc545d12fa8aca00452792de54e3fae74993260cd69afee0fa
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.2.0.tgz
version: 2.2.0
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.626328122Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: beac1182a39f158fb9aa1f3308b4b030bf378d612f2aa860f792fad62aa30321
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.1.1.tgz
version: 2.1.1
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.625121312Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: efb6cbd932a7ac082853d305e970db72e0086543a6963baabef16ebd2e8498f8
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.1.0.tgz
version: 2.1.0
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.623735055Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: 46f25124d9045802d0e50d34209d15a7fa15e1ef1c9d3f0e93ac4bb39b7c9b17
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.5.tgz
version: 2.0.5
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.622870733Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: db82381ffe831b07ce7777e8e6e05455a8eaeccfcd0afcd87825433a96cb2d65
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.4.tgz
version: 2.0.4
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.622004867Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: 872919ea64531b35dfa7f956d8a6e4130a1c7f0f80c50141b6f2d1cddd49682e
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.3.tgz
version: 2.0.3
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.621180038Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: 7cbb5379f8b41bc938a29b44c563757358c25fd843c75fb6d2f1a2b9365c0cf1
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.1-dev.tgz
version: 2.0.1-dev
- apiVersion: v2
appVersion: latest
created: "2026-03-11T12:15:44.620303012Z"
description: Dev Container with AI coding agents and MCP sidecars - supports persistent
and dynamic deployment modes
digest: 5736c92626e2c3edb8e81ddba5be581360dbf3901ccc607db8bad238ea831cd0
keywords:
- development
- devcontainer
- vscode
- ai
- knative
- serverless
name: devcontainer
type: application
urls:
- https://farhoodliquor.github.io/devcontainer/devcontainer-2.0.0-dev.tgz
version: 2.0.0-dev
generated: "2026-03-11T12:15:44.619306973Z"
-30
View File
@@ -1,30 +0,0 @@
# Antigravity Dev Container - Session Notes
## Key Architecture Facts
- Image: `ghcr.io/cpfarhood/devcontainer:latest` (repo name is `devcontainer`, not `antigravity`)
- Deployed via Helm chart (`chart/`), not kustomize anymore
- Service must NOT be headless (`clusterIP: None`) — Cilium gateway can't route to headless services
- `SECURE_CONNECTION=0` — TLS is terminated at the gateway, not the app
- Container user is `user` (UID 1000) — baseimage-gui runs startapp.sh as `app` user, sudo is not available
## Deployment Method
- **Primary**: Helm chart in `chart/` directory
- **Makefile targets**: `helm-deploy`, `helm-delete`, `helm-logs`, `helm-shell`, `helm-port-forward`
- **Old kustomize** (`k8s/` directory) has been removed — all deployments use Helm now
- Chart published as OCI artifact to GHCR, reconciled by Flux
## MCP Sidecars
- **Kubernetes MCP** (v0.0.57, port 8080): Only deployed when enabled AND `clusterAccess` != `none`
- **Flux MCP** (v0.41.1, port 8081): Only deployed when enabled AND `clusterAccess` != `none`
- **Home Assistant MCP** (6.7.1, port 8087): Disabled by default, requires secrets:
- `homeassistant-url`: Base URL like `http://homeassistant.local:8123`
- `homeassistant-token`: Long-lived access token
- **Playwright MCP**: External service, not a sidecar
- Configure via `mcpSidecars.<name>.enabled` in values
- **Version Strategy**: All MCP images use pinned versions for stability (no `latest` tags)
## Common Gotchas
- `baseimage-gui` creates user dynamically — don't hardcode usernames in scripts, use numeric UID/GID
- `chown /home` fails (PVC root not owned by container) — only chown subdirectories
- `sudo` not available in startapp.sh — script already runs as correct user
- MCP sidecars need appropriate secrets and RBAC permissions to function
-72
View File
@@ -1,72 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":gitSignOff"
],
"semanticCommits": "enabled",
"dependencyDashboard": true,
"suppressNotifications": [
"prEditedNotification"
],
"rebaseWhen": "conflicted",
"commitMessagePrefix": "chore(deps):",
"commitMessageAction": "update",
"commitMessageTopic": "{{depName}}",
"prConcurrentLimit": 5,
"prHourlyLimit": 2,
"schedule": [
"before 6am on monday"
],
"packageRules": [
{
"description": "GitHub Actions",
"matchManagers": [
"github-actions"
],
"groupName": "github-actions",
"additionalBranchPrefix": "github-actions-",
"semanticCommitScope": "github-actions",
"pinDigests": true
},
{
"description": "Docker base image",
"matchManagers": [
"dockerfile"
],
"groupName": "docker",
"additionalBranchPrefix": "docker-",
"semanticCommitScope": "docker"
},
{
"description": "Automerge patch updates",
"matchUpdateTypes": [
"patch"
],
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true
},
{
"description": "Automerge minor updates for stable packages",
"matchUpdateTypes": [
"minor"
],
"matchCurrentVersion": "!/^0/",
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true
},
{
"description": "Separate major updates - require manual review",
"matchUpdateTypes": [
"major"
],
"automerge": false,
"additionalBranchPrefix": "major-"
}
],
"ignorePaths": [
"**/node_modules/**"
]
}
-45
View File
@@ -1,45 +0,0 @@
#!/bin/sh
# Start OpenSSH server when SSH=true.
# Runs as root during container initialisation (cont-init.d).
[ "${SSH:-false}" = "true" ] || exit 0
echo "=== SSH enabled: starting sshd ==="
HOME_DIR="/config/userdata"
HOST_KEY_STORE="$HOME_DIR/.ssh/host_keys"
# Persist host keys on the home PVC so clients don't see a "host key
# changed" warning after pod restarts.
if [ -d "$HOST_KEY_STORE" ] && [ -n "$(ls "$HOST_KEY_STORE"/ssh_host_* 2>/dev/null)" ]; then
# Restore previously generated host keys
echo "Restoring SSH host keys from PVC..."
cp "$HOST_KEY_STORE"/ssh_host_* /etc/ssh/
chmod 600 /etc/ssh/ssh_host_*_key
chmod 644 /etc/ssh/ssh_host_*_key.pub
else
# First boot: generate and save host keys to PVC
echo "Generating SSH host keys (first boot)..."
ssh-keygen -A 2>/dev/null || true
mkdir -p "$HOST_KEY_STORE"
cp /etc/ssh/ssh_host_* "$HOST_KEY_STORE/"
chmod 700 "$HOST_KEY_STORE"
chown -R 1000:1000 "$HOST_KEY_STORE"
echo "SSH host keys saved to PVC."
fi
# Populate authorized_keys from env var (injected via Kubernetes secret)
if [ -n "$SSH_AUTHORIZED_KEYS" ]; then
mkdir -p "$HOME_DIR/.ssh"
chmod 700 "$HOME_DIR/.ssh"
printf '%s\n' "$SSH_AUTHORIZED_KEYS" > "$HOME_DIR/.ssh/authorized_keys"
chmod 600 "$HOME_DIR/.ssh/authorized_keys"
chown -R 1000:1000 "$HOME_DIR/.ssh"
echo "SSH authorized keys configured."
else
echo "WARNING: SSH_AUTHORIZED_KEYS not set — you will not be able to log in."
fi
# Start sshd in background (root required to bind :22 and fork sessions)
/usr/sbin/sshd -D &
echo "sshd started (PID $!)"
-10
View File
@@ -1,10 +0,0 @@
#!/bin/sh
# Fix the app user (UID 1000) created by baseimage-gui at runtime.
# baseimage-gui sets shell=/sbin/nologin and home=/dev/null, which
# prevents VSCode from opening terminals.
if id app >/dev/null 2>&1; then
usermod -s /bin/bash app
usermod -d /config/userdata app
else
echo "WARNING: 'app' user not found, skipping usermod" >&2
fi
-117
View File
@@ -1,117 +0,0 @@
#!/bin/bash
# Initialize repository
set -e
echo "=== Repository Initialization ==="
# Set up basic git configuration
echo "Configuring git user settings..."
# Use environment variables if provided, otherwise use defaults
GIT_USER_NAME="${GIT_USER_NAME:-DevContainer User}"
GIT_USER_EMAIL="${GIT_USER_EMAIL:-devcontainer@example.com}"
git config --global user.name "$GIT_USER_NAME"
git config --global user.email "$GIT_USER_EMAIL"
# Set up git credentials early if GITHUB_TOKEN is provided
# This ensures all git operations have proper authentication
if [ -n "$GITHUB_TOKEN" ]; then
echo "Setting up git credentials..."
# Configure git to use credential store globally
git config --global credential.helper store
# Create or update the credentials file
CREDENTIALS_FILE="/config/userdata/.git-credentials"
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
# Support multiple git hosting providers
# GitHub supports both oauth2 and token as username
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > "$CREDENTIALS_FILE"
echo "https://${GITHUB_TOKEN}:x-oauth-basic@github.com" >> "$CREDENTIALS_FILE"
echo "https://token:${GITHUB_TOKEN}@github.com" >> "$CREDENTIALS_FILE"
# GitLab format (if same token works)
if [ -n "$GITLAB_HOST" ]; then
echo "https://oauth2:${GITHUB_TOKEN}@${GITLAB_HOST}" >> "$CREDENTIALS_FILE"
fi
chmod 600 "$CREDENTIALS_FILE"
# Also create a symlink in the home directory if it doesn't exist
# This handles cases where git might look in different locations
if [ ! -f "$HOME/.git-credentials" ] && [ "$HOME" != "/config/userdata" ]; then
ln -sf "$CREDENTIALS_FILE" "$HOME/.git-credentials"
fi
echo "Git credentials configured"
else
# Even without a token, ensure git has a proper credential helper configured
# This prevents errors when credentials are added later
echo "No GITHUB_TOKEN provided, configuring basic git settings..."
git config --global credential.helper store
# Create an empty credentials file with proper permissions
CREDENTIALS_FILE="/config/userdata/.git-credentials"
mkdir -p "$(dirname "$CREDENTIALS_FILE")"
touch "$CREDENTIALS_FILE"
chmod 600 "$CREDENTIALS_FILE"
# Create symlink if needed
if [ ! -f "$HOME/.git-credentials" ] && [ "$HOME" != "/config/userdata" ]; then
ln -sf "$CREDENTIALS_FILE" "$HOME/.git-credentials"
fi
fi
# Check if GITHUB_REPO is set
if [ -z "$GITHUB_REPO" ]; then
echo "GITHUB_REPO not set, skipping repository clone"
WORKSPACE_DIR="/workspace/default"
mkdir -p "$WORKSPACE_DIR"
else
# Parse repo name from URL
REPO_NAME=$(basename "$GITHUB_REPO" .git)
WORKSPACE_DIR="/workspace/$REPO_NAME"
echo "Repository: $GITHUB_REPO"
echo "Target directory: $WORKSPACE_DIR"
# Check if repo already exists
if [ -d "$WORKSPACE_DIR/.git" ]; then
echo "Repository already exists, pulling latest changes..."
cd "$WORKSPACE_DIR"
git pull || echo "Pull failed, continuing anyway..."
else
echo "Cloning repository..."
mkdir -p "$(dirname "$WORKSPACE_DIR")"
# Clone with token if provided
if [ -n "$GITHUB_TOKEN" ]; then
# Replace https://github.com/ with https://oauth2:token@github.com/
CLONE_URL=$(echo "$GITHUB_REPO" | sed "s|https://github.com/|https://oauth2:${GITHUB_TOKEN}@github.com/|")
git clone "$CLONE_URL" "$WORKSPACE_DIR"
else
git clone "$GITHUB_REPO" "$WORKSPACE_DIR"
fi
fi
fi
# Set ownership using numeric IDs (username may not exist yet in baseimage-gui)
RUN_UID="${USER_ID:-1000}"
RUN_GID="${GROUP_ID:-1000}"
chown -R "$RUN_UID:$RUN_GID" "$WORKSPACE_DIR"
# Ensure home directory exists on the PVC (may be absent on a fresh volume)
mkdir -p "$HOME"
chown "$RUN_UID:$RUN_GID" "$HOME"
# Seed Claude Code settings if missing (disable auto-updater in Docker)
if [ ! -f "$HOME/.claude/settings.json" ]; then
mkdir -p "$HOME/.claude"
echo '{"env":{"DISABLE_AUTOUPDATER":"1"}}' > "$HOME/.claude/settings.json"
chown -R "$RUN_UID:$RUN_GID" "$HOME/.claude"
fi
# Export workspace directory for startapp.sh
echo "$WORKSPACE_DIR" > /tmp/workspace-dir
echo "=== Initialization Complete ==="
-49
View File
@@ -1,49 +0,0 @@
#!/bin/bash
# Start application script for baseimage-gui
set -e
echo "=== Starting Dev Container ==="
# Check if we're in serverless mode
if [[ "$SERVERLESS_MODE" == "true" ]]; then
echo "Serverless mode detected, using serverless startup script..."
exec /usr/local/bin/serverless-startapp
fi
# Traditional mode - initialize repository
/usr/local/bin/init-repo
# Get workspace directory
if [ -f /tmp/workspace-dir ]; then
WORKSPACE_DIR=$(cat /tmp/workspace-dir)
else
WORKSPACE_DIR="/workspace/default"
fi
IDE="${IDE:-vscode}"
echo "IDE mode: $IDE"
echo "Workspace: $WORKSPACE_DIR"
case "$IDE" in
antigravity)
echo "Opening Google Antigravity in: $WORKSPACE_DIR"
# --no-sandbox is required for Electron apps in Docker (no kernel sandbox available).
# Explicit --user-data-dir and --extensions-dir pin config to the home PVC so
# settings and the setup wizard state survive pod restarts.
exec antigravity --no-sandbox \
--user-data-dir "$HOME/.config/antigravity" \
--extensions-dir "$HOME/.antigravity/extensions" \
--new-window --wait "$WORKSPACE_DIR"
;;
none)
echo "IDE=none: no IDE launched, keeping container alive."
exec sleep infinity
;;
*)
if [ "$IDE" != "vscode" ]; then
echo "WARNING: Unknown IDE value '$IDE', defaulting to VSCode"
fi
echo "Opening VSCode in: $WORKSPACE_DIR"
exec code --new-window --wait "$WORKSPACE_DIR"
;;
esac
-46
View File
@@ -1,46 +0,0 @@
#!/bin/bash
# Test script to verify git credentials configuration
set -e
echo "=== Git Credentials Test ==="
# Check git configuration
echo "1. Git user configuration:"
git config --global user.name || echo " ❌ user.name not set"
git config --global user.email || echo " ❌ user.email not set"
echo ""
echo "2. Git credential helper:"
git config --global credential.helper || echo " ❌ credential.helper not set"
echo ""
echo "3. Credentials file locations:"
CREDENTIALS_FILE="/config/userdata/.git-credentials"
if [ -f "$CREDENTIALS_FILE" ]; then
echo "$CREDENTIALS_FILE exists"
echo " Permissions: $(stat -c %a $CREDENTIALS_FILE)"
echo " Lines in file: $(wc -l < $CREDENTIALS_FILE)"
else
echo "$CREDENTIALS_FILE does not exist"
fi
if [ -f "$HOME/.git-credentials" ]; then
if [ -L "$HOME/.git-credentials" ]; then
echo "$HOME/.git-credentials is a symlink to $(readlink -f $HOME/.git-credentials)"
else
echo "$HOME/.git-credentials exists (not a symlink)"
fi
else
echo "$HOME/.git-credentials does not exist"
fi
echo ""
echo "4. Environment check:"
echo " HOME=$HOME"
echo " GITHUB_TOKEN=${GITHUB_TOKEN:+[SET]}"
echo " GIT_USER_NAME=${GIT_USER_NAME:-[NOT SET]}"
echo " GIT_USER_EMAIL=${GIT_USER_EMAIL:-[NOT SET]}"
echo ""
echo "=== Test Complete ==="
-173
View File
@@ -1,173 +0,0 @@
# DevContainer Serverless 2.0 Makefile
# Configuration
REGISTRY ?= ghcr.io/cpfarhood
ROUTING_PROXY_IMAGE := $(REGISTRY)/devcontainer-routing-proxy
DEVCONTAINER_IMAGE := $(REGISTRY)/devcontainer
VERSION ?= 2.0.0-alpha
NAMESPACE := devcontainers
# Knative service name
KN_SERVICE := devcontainer-serverless
.PHONY: help build push deploy test clean
help: ## Display this help message
@echo "DevContainer Serverless 2.0"
@echo ""
@echo "Available targets:"
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-15s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
# Build targets
build-routing-proxy: ## Build the routing proxy image
@echo "Building routing proxy image..."
cd routing-proxy && docker build -t $(ROUTING_PROXY_IMAGE):$(VERSION) .
docker tag $(ROUTING_PROXY_IMAGE):$(VERSION) $(ROUTING_PROXY_IMAGE):latest
build-devcontainer: ## Build the main devcontainer image (from parent directory)
@echo "Building devcontainer image..."
cd .. && docker build -t $(DEVCONTAINER_IMAGE):$(VERSION) .
docker tag $(DEVCONTAINER_IMAGE):$(VERSION) $(DEVCONTAINER_IMAGE):latest
build: build-routing-proxy build-devcontainer ## Build all images
# Push targets
push-routing-proxy: build-routing-proxy ## Push routing proxy image
@echo "Pushing routing proxy image..."
docker push $(ROUTING_PROXY_IMAGE):$(VERSION)
docker push $(ROUTING_PROXY_IMAGE):latest
push-devcontainer: build-devcontainer ## Push devcontainer image
@echo "Pushing devcontainer image..."
docker push $(DEVCONTAINER_IMAGE):$(VERSION)
docker push $(DEVCONTAINER_IMAGE):latest
push: push-routing-proxy push-devcontainer ## Push all images
# Deployment targets
create-namespace: ## Create the devcontainers namespace
@echo "Creating namespace..."
kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f -
deploy-secrets: create-namespace ## Deploy secrets (update values first!)
@echo "Deploying secrets..."
@echo "WARNING: Update the secret values in deployment.yaml first!"
kubectl apply -f deployment.yaml
@echo "Don't forget to update the secret with real values:"
@echo "kubectl edit secret devcontainer-serverless-secrets -n $(NAMESPACE)"
deploy-components: create-namespace ## Deploy routing proxy and Knative service
@echo "Deploying serverless components..."
kubectl apply -f deployment.yaml
deploy: deploy-secrets deploy-components ## Deploy everything
# Configuration targets
configure-authentik: ## Apply Authentik configuration
@echo "Applying Authentik configuration..."
kubectl apply -f authentik-config.yaml
@echo "Complete the setup in Authentik web UI:"
@echo "1. Create Forward Auth Provider"
@echo "2. Create Application"
@echo "3. Create Outpost"
# Testing targets
test-routing-proxy: ## Test routing proxy locally
@echo "Testing routing proxy..."
@echo "Starting local test..."
cd routing-proxy && docker run --rm -d --name devcontainer-routing-test \
-p 8080:8080 \
-e DEVCONTAINER_SERVICE_URL=httpbin.org \
$(ROUTING_PROXY_IMAGE):latest
@echo "Testing GitHub repo extraction..."
sleep 2
curl -v "http://localhost:8080/github/microsoft/vscode" || true
docker stop devcontainer-routing-test
@echo "Test complete!"
test-knative: ## Test Knative service deployment
@echo "Testing Knative service..."
kubectl get ksvc $(KN_SERVICE) -n $(NAMESPACE)
kubectl describe ksvc $(KN_SERVICE) -n $(NAMESPACE)
test: test-routing-proxy test-knative ## Run all tests
# Status and debugging targets
status: ## Show status of all components
@echo "=== Namespace ==="
kubectl get ns $(NAMESPACE) || echo "Namespace not found"
@echo ""
@echo "=== Routing Proxy ==="
kubectl get deployment devcontainer-routing-proxy -n $(NAMESPACE) || echo "Routing proxy not found"
@echo ""
@echo "=== Knative Service ==="
kubectl get ksvc $(KN_SERVICE) -n $(NAMESPACE) || echo "Knative service not found"
@echo ""
@echo "=== Pods ==="
kubectl get pods -n $(NAMESPACE)
@echo ""
@echo "=== Ingress ==="
kubectl get ingress -n $(NAMESPACE)
logs-routing-proxy: ## Show routing proxy logs
kubectl logs -n $(NAMESPACE) deployment/devcontainer-routing-proxy -f
logs-knative: ## Show Knative service logs
kubectl logs -n $(NAMESPACE) -l serving.knative.dev/service=$(KN_SERVICE) -f
# Cleanup targets
clean-pods: ## Delete all pods in the namespace
kubectl delete pods --all -n $(NAMESPACE)
clean-deployment: ## Delete the serverless deployment
kubectl delete -f deployment.yaml --ignore-not-found
clean-namespace: ## Delete the entire namespace
kubectl delete namespace $(NAMESPACE) --ignore-not-found
clean: clean-deployment ## Clean up deployment
# Development targets
dev-setup: ## Set up development environment
@echo "Setting up development environment..."
@echo "Prerequisites:"
@echo "- Kubernetes cluster with Knative Serving"
@echo "- kubectl configured"
@echo "- Docker for building images"
@echo ""
@echo "Run 'make build deploy' to get started"
scale-to-zero: ## Force Knative service to scale to zero
@echo "Scaling Knative service to zero..."
kubectl patch ksvc $(KN_SERVICE) -n $(NAMESPACE) --type='merge' -p='{"spec":{"template":{"metadata":{"annotations":{"autoscaling.knative.dev/minScale":"0"}}}}}'
scale-up: ## Trigger a scale-up of the Knative service
@echo "Triggering scale-up..."
curl -H "X-GitHub-Repo: https://github.com/microsoft/vscode" \
"http://devcontainer-routing-proxy.$(NAMESPACE).svc.cluster.local/github/microsoft/vscode" || \
kubectl run curl --rm -i --restart=Never --image=curlimages/curl -- \
-H "X-GitHub-Repo: https://github.com/microsoft/vscode" \
"http://devcontainer-routing-proxy.$(NAMESPACE).svc.cluster.local/github/microsoft/vscode"
# Documentation targets
docs: ## Generate documentation
@echo "Documentation files:"
@echo "- README.md: Main documentation"
@echo "- deployment.yaml: Kubernetes manifests"
@echo "- authentik-config.yaml: Authentik configuration"
@echo ""
@echo "View online documentation at: https://github.com/cpfarhood/devcontainer/tree/feature/serverless-2.0.0/serverless"
# Version management
version: ## Show current version
@echo "Version: $(VERSION)"
@echo "Registry: $(REGISTRY)"
@echo "Images:"
@echo " - $(ROUTING_PROXY_IMAGE):$(VERSION)"
@echo " - $(DEVCONTAINER_IMAGE):$(VERSION)"
# Quick development workflow
dev: build deploy status ## Quick development: build, deploy, show status
# Production deployment workflow
prod: build push deploy configure-authentik status ## Production deployment workflow
-376
View File
@@ -1,376 +0,0 @@
# DevContainer Serverless 2.0
A serverless, auto-scaling development container platform with dynamic GitHub repository routing, secured by Authentik authentication.
## Architecture Overview
```
User Request: https://devcontainer.farh.net/github/microsoft/vscode
Authentik (Authentication & Authorization)
↓ (authenticated request with user headers)
NGINX Ingress (SSL termination, rate limiting)
Routing Proxy (extracts GitHub repo from URL, adds headers)
↓ (with X-GitHub-Repo header)
Knative Service (devcontainer-serverless)
↓ (auto-scales from 0 to N instances)
Dev Container Instances (ephemeral, repo-specific)
```
### Key Features
- 🚀 **Scale to Zero**: Containers automatically scale down to zero when not in use
- 🔐 **Authentik Integration**: Full authentication and authorization via Authentik
- 🐙 **Dynamic GitHub Routing**: Access any repo via `/github/{owner}/{repo}`
-**Fast Cold Start**: Optimized startup for quick repository access
- 📁 **Built-in File Manager**: Upload/download files via web interface
- 🛠️ **Multiple IDEs**: VSCode, Antigravity, or headless mode
- 🎯 **Per-User Isolation**: Each request gets its own container instance
## Quick Start
### Prerequisites
- Kubernetes cluster with Knative Serving installed
- Authentik deployed and configured
- NGINX Ingress Controller
- cert-manager for SSL certificates
### 1. Deploy the Serverless Components
```bash
# Create namespace and deploy all components
kubectl apply -f serverless/deployment.yaml
# Build and push the routing proxy image
cd serverless/routing-proxy
docker build -t ghcr.io/cpfarhood/devcontainer-routing-proxy:latest .
docker push ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
```
### 2. Configure Authentik
```bash
# Apply Authentik configuration
kubectl apply -f serverless/authentik-config.yaml
# Configure the application via Authentik web UI:
# 1. Go to Applications > Providers > Create
# 2. Type: Forward Auth (single application)
# 3. Name: devcontainer-forward-auth-provider
# 4. External host: https://devcontainer.farh.net
# 5. Create the Application pointing to this provider
```
### 3. Update DNS and SSL
```bash
# Point devcontainer.farh.net to your ingress controller
# The cert-manager will automatically provision SSL certificates
```
### 4. Test the Deployment
```bash
# Visit in browser (will redirect to Authentik for login)
https://devcontainer.farh.net/github/microsoft/vscode
# Check pod scaling
kubectl get pods -n devcontainers -w
# View logs
kubectl logs -n devcontainers deployment/devcontainer-routing-proxy -f
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless -f
```
## Usage
### URL Format
```
https://devcontainer.farh.net/github/{owner}/{repo}
```
### Examples
```bash
# Microsoft VSCode
https://devcontainer.farh.net/github/microsoft/vscode
# Kubernetes
https://devcontainer.farh.net/github/kubernetes/kubernetes
# Your private repo (requires GitHub token)
https://devcontainer.farh.net/github/yourorg/private-repo
```
### Authentication Flow
1. User visits `https://devcontainer.farh.net/github/owner/repo`
2. NGINX Ingress checks with Authentik for authentication
3. If not authenticated, redirects to Authentik login
4. After successful login, request proceeds with user headers
5. Routing proxy extracts repository from URL
6. Knative spins up (or reuses) a container instance
7. Container clones the specified repository and starts IDE
### File Upload/Download
Each container includes a built-in file manager accessible via the VNC web interface:
1. Connect to your dev container via the browser
2. Look for the file manager icon in the VNC toolbar
3. Upload/download files directly through the web interface
## Configuration
### Environment Variables (Secret)
Update the secret in `serverless/deployment.yaml`:
```yaml
stringData:
GITHUB_TOKEN: "ghp_your_github_token" # For private repositories
VNC_PASSWORD: "your_secure_password" # VNC access password
ANTHROPIC_API_KEY: "sk-ant-your_key" # Claude API key
GIT_USER_NAME: "Your Name" # Git commit author
GIT_USER_EMAIL: "your.email@example.com" # Git commit email
```
### Scaling Configuration
Modify the Knative Service annotations in `deployment.yaml`:
```yaml
annotations:
autoscaling.knative.dev/minScale: "0" # Scale to zero
autoscaling.knative.dev/maxScale: "20" # Max instances
autoscaling.knative.dev/target: "1" # 1 request per pod
autoscaling.knative.dev/scale-to-zero-grace-period: "10m"
```
### Resource Limits
Adjust per-instance resources:
```yaml
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "8Gi" # More memory for large repos
cpu: "4000m" # More CPU for compilation tasks
```
### IDE Selection
Set the default IDE via environment variable:
```yaml
env:
- name: IDE
value: "vscode" # Options: vscode, antigravity, none
```
## Monitoring and Observability
### Health Checks
```bash
# Routing proxy health
curl http://devcontainer-routing-proxy.devcontainers.svc.cluster.local/health
# Knative service status
kn service describe devcontainer-serverless -n devcontainers
# Check container logs
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless -f
```
### Metrics
The setup includes Prometheus integration:
- **Authentik metrics**: User authentication events
- **Knative metrics**: Container scaling, cold starts, request latency
- **NGINX metrics**: Request rates, response times
- **Container metrics**: Resource usage per repository
### Grafana Dashboards
Import the provided dashboard for monitoring:
```bash
# TODO: Create Grafana dashboard JSON
```
## Security Considerations
### Network Policies
```yaml
# Restrict networking between components
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: devcontainer-serverless-network-policy
namespace: devcontainers
spec:
podSelector:
matchLabels:
serving.knative.dev/service: devcontainer-serverless
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/component: routing-proxy
ports:
- protocol: TCP
port: 5800
egress:
- to: [] # Allow all outbound (needed for git clone, package installs)
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 80
```
### Repository Access Control
Configure Authentik policies to control repository access:
```python
# Example Authentik expression policy
github_repo = request.http_request.headers.get('X-GitHub-Repo', '')
user_groups = [g.name for g in request.user.ak_groups.all()]
# Allow admins access to everything
if 'admins' in user_groups:
return True
# Allow developers access to public repos and specific private repos
if 'developers' in user_groups:
# Add logic for private repository access based on user attributes
if 'private-repo-access' in user.ak_attributes:
allowed_repos = user.ak_attributes['private-repo-access']
return github_repo in allowed_repos
return True # Public repos only
return False
```
## Troubleshooting
### Common Issues
1. **Container won't start**
```bash
# Check Knative service status
kn service describe devcontainer-serverless -n devcontainers
# Check pod events
kubectl describe pod -n devcontainers -l serving.knative.dev/service=devcontainer-serverless
```
2. **Repository clone fails**
```bash
# Check GitHub token in secret
kubectl get secret devcontainer-serverless-secrets -n devcontainers -o yaml
# Check container logs for git errors
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless --tail=100
```
3. **Authentik authentication loop**
```bash
# Check Authentik outpost logs
kubectl logs -n authentik -l app.kubernetes.io/name=authentik
# Verify ingress annotations
kubectl describe ingress devcontainer-serverless-ingress -n devcontainers
```
4. **Slow cold starts**
```bash
# Check container startup time
kubectl logs -n devcontainers -l serving.knative.dev/service=devcontainer-serverless --timestamps
# Consider increasing timeout
# serving.knative.dev/timeoutSeconds: "900" # 15 minutes
```
### Performance Tuning
1. **Reduce cold start time**:
- Use minimal base image layers
- Pre-install common development tools
- Optimize git clone (shallow clone for large repos)
2. **Resource optimization**:
- Set appropriate resource requests/limits
- Use `autoscaling.knative.dev/target-utilization-percentage`
- Consider persistent volumes for frequently accessed repos
3. **Network optimization**:
- Use private container registry for faster image pulls
- Configure image pull policies appropriately
- Consider using a git cache proxy
## Development
### Building the Routing Proxy
```bash
cd serverless/routing-proxy
docker build -t ghcr.io/cpfarhood/devcontainer-routing-proxy:v2.0.0 .
docker push ghcr.io/cpfarhood/devcontainer-routing-proxy:v2.0.0
```
### Testing Locally
```bash
# Run the routing proxy locally
cd serverless/routing-proxy
docker run -p 8080:8080 \
-e DEVCONTAINER_SERVICE_URL=host.docker.internal:5800 \
ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
# Test routing
curl -H "X-GitHub-Repo: https://github.com/microsoft/vscode" \
http://localhost:8080/github/microsoft/vscode
```
### Contributing
1. Create feature branch from `feature/serverless-2.0.0`
2. Make changes to serverless components
3. Test with local Knative setup
4. Submit pull request
## Migration from 1.x
The serverless 2.0 architecture is a complete redesign. Migration steps:
1. **Backup existing data**: Export user configs, git credentials
2. **Deploy 2.0 components**: Following the quick start guide
3. **Migrate users**: Update Authentik with existing user accounts
4. **Test extensively**: Verify repository access and functionality
5. **Switch DNS**: Point domain to new infrastructure
6. **Cleanup 1.x**: Remove old Helm deployments
## Roadmap
- [ ] GitLab support (`/gitlab/group/project`)
- [ ] Bitbucket support
- [ ] Repository templates and scaffolding
- [ ] Collaborative editing features
- [ ] IDE plugins and extensions management
- [ ] Resource quotas per user/group
- [ ] Repository caching and optimization
- [ ] Integration with CI/CD pipelines
-168
View File
@@ -1,168 +0,0 @@
# Authentik configuration for DevContainer serverless auth
# This assumes Authentik is already deployed in the 'authentik' namespace
---
# Application definition for DevContainer Serverless
apiVersion: v1
kind: ConfigMap
metadata:
name: authentik-devcontainer-app-config
namespace: authentik
data:
# This will be applied via Authentik API or web interface
application.yaml: |
name: DevContainer Serverless
slug: devcontainer-serverless
provider: devcontainer-forward-auth-provider
launch_url: https://devcontainer.farh.net/
open_in_new_tab: true
meta_description: "Serverless development containers with dynamic GitHub repository routing"
meta_publisher: "DevContainer Team"
policy_engine_mode: "all"
group: "Development Tools"
---
# Forward Auth Provider configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: authentik-devcontainer-provider-config
namespace: authentik
data:
provider.yaml: |
name: devcontainer-forward-auth-provider
authorization_flow: default-authorization-flow # Use your default flow
external_host: https://devcontainer.farh.net
# Advanced settings
token_validity: hours=24 # Long-lived sessions for dev work
# Headers to forward to the application
# These will be available as HTTP_* environment variables in containers
property_mappings:
- "authentik_core.x-authentik-username"
- "authentik_core.x-authentik-email"
- "authentik_core.x-authentik-name"
- "authentik_core.x-authentik-groups"
---
# Outpost configuration for forward auth
apiVersion: v1
kind: ConfigMap
metadata:
name: authentik-devcontainer-outpost-config
namespace: authentik
data:
outpost.yaml: |
name: devcontainer-forward-auth-outpost
type: proxy
providers:
- devcontainer-forward-auth-provider
# Outpost configuration
config:
authentik_host: https://auth.farh.net
authentik_host_insecure: false
authentik_host_browser: https://auth.farh.net
# Log level for debugging
log_level: info
# Cookie settings
cookie_domain: .farh.net
cookie_secure: true
# NGINX ingress integration
external_host: https://devcontainer.farh.net
internal_host: http://authentik.authentik.svc.cluster.local
# Forward auth specific settings
mode: forward_single
skip_path_regex: "^/(health|metrics)$" # Skip auth for health checks
---
# Example NGINX Ingress annotations for reference
# (These go in the main ingress resource)
apiVersion: v1
kind: ConfigMap
metadata:
name: authentik-nginx-annotations
namespace: devcontainers
data:
annotations.yaml: |
# Forward auth configuration
nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx
nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri
nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Host $http_host;
# Additional headers for the application
nginx.ingress.kubernetes.io/server-snippet: |
location ~ ^/github/([^/]+/[^/]+) {
# Log the GitHub repo being accessed
access_log /var/log/nginx/devcontainer-access.log combined;
# Set additional headers for audit/monitoring
proxy_set_header X-GitHub-Repo-Requested https://github.com/$1;
proxy_set_header X-Request-Timestamp $time_iso8601;
proxy_set_header X-Client-IP $remote_addr;
}
---
# Policy for controlling access (optional - can be configured via Authentik UI)
apiVersion: v1
kind: ConfigMap
metadata:
name: authentik-devcontainer-policies
namespace: authentik
data:
# Example group-based access policy
group-access-policy.yaml: |
name: DevContainer Access Policy
policy_type: group_membership
groups:
- developers
- devops
- admins
# Example expression policy for advanced access control
repo-access-policy.yaml: |
name: Repository Access Policy
policy_type: expression
expression: |
# Allow access to public repositories for all authenticated users
# Require specific groups for private repositories
github_repo = request.http_request.headers.get('X-GitHub-Repo', '')
# Check if user has access to private repositories
if 'private-repo-access' in user.ak_groups.values_list('name', flat=True):
return True
# For now, allow all authenticated users to access any repository
# You can customize this based on your needs
return True
---
# Service Monitor for Prometheus (optional)
apiVersion: v1
kind: ConfigMap
metadata:
name: authentik-devcontainer-monitoring
namespace: authentik
data:
servicemonitor.yaml: |
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: devcontainer-authentik
namespace: authentik
spec:
selector:
matchLabels:
app.kubernetes.io/name: authentik
endpoints:
- port: http
interval: 30s
path: /metrics
-243
View File
@@ -1,243 +0,0 @@
---
# Namespace for serverless components
apiVersion: v1
kind: Namespace
metadata:
name: devcontainers
labels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: serverless
---
# Secret for GitHub tokens, VNC passwords, etc.
apiVersion: v1
kind: Secret
metadata:
name: devcontainer-serverless-secrets
namespace: devcontainers
type: Opaque
stringData:
# Update these values as needed
GITHUB_TOKEN: ""
VNC_PASSWORD: "changeme"
ANTHROPIC_API_KEY: ""
GIT_USER_NAME: "DevContainer User"
GIT_USER_EMAIL: "devcontainer@example.com"
---
# Routing proxy deployment (handles GitHub repo extraction)
apiVersion: apps/v1
kind: Deployment
metadata:
name: devcontainer-routing-proxy
namespace: devcontainers
labels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
spec:
replicas: 2 # High availability
selector:
matchLabels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
template:
metadata:
labels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
spec:
containers:
- name: routing-proxy
image: ghcr.io/cpfarhood/devcontainer-routing-proxy:latest
ports:
- containerPort: 8080
name: http
env:
- name: DEVCONTAINER_SERVICE_URL
value: "devcontainer-serverless.devcontainers.svc.cluster.local"
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
---
# Service for routing proxy
apiVersion: v1
kind: Service
metadata:
name: devcontainer-routing-proxy
namespace: devcontainers
labels:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
name: http
selector:
app.kubernetes.io/name: devcontainer
app.kubernetes.io/component: routing-proxy
---
# Knative Service (auto-scaling devcontainer instances)
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: devcontainer-serverless
namespace: devcontainers
annotations:
# Scale to zero when not in use (saves resources)
autoscaling.knative.dev/minScale: "0"
autoscaling.knative.dev/maxScale: "10"
# Keep instances warm for 5 minutes after last request
autoscaling.knative.dev/scale-to-zero-grace-period: "5m"
# Target 1 concurrent request per pod (ensures isolation)
autoscaling.knative.dev/target: "1"
# Custom domain (optional - configure after Authentik setup)
# serving.knative.dev/domain: "devcontainer.farh.net"
spec:
template:
metadata:
annotations:
# Container port for VNC web interface
autoscaling.knative.dev/targetPort: "5800"
# Timeout for cold starts (dev containers need time to initialize)
serving.knative.dev/timeoutSeconds: "600" # 10 minutes for repo cloning
# Resource allocation per instance
autoscaling.knative.dev/class: "kpa.autoscaling.knative.dev"
autoscaling.knative.dev/metric: "concurrency"
spec:
# Give containers more time to start (repo cloning + IDE launch)
timeoutSeconds: 600 # 10 minutes
containers:
- name: devcontainer
image: ghcr.io/cpfarhood/devcontainer:latest
ports:
- containerPort: 5800
name: vnc-web
env:
# Flag to indicate serverless mode
- name: SERVERLESS_MODE
value: "true"
- name: DYNAMIC_GITHUB_ROUTING
value: "true"
- name: IDE
value: "vscode"
- name: DISPLAY_WIDTH
value: "1920"
- name: DISPLAY_HEIGHT
value: "1080"
- name: SECURE_CONNECTION
value: "0"
- name: USER_ID
value: "1000"
- name: GROUP_ID
value: "1000"
# Enable file manager for easy upload/download
- name: WEB_FILE_MANAGER
value: "1"
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
value: "/workspace,/config"
# Use secrets for sensitive data
envFrom:
- secretRef:
name: devcontainer-serverless-secrets
optional: false
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
volumeMounts:
- name: tmp-home
mountPath: /config
- name: shm
mountPath: /dev/shm
# Readiness probe - VNC must be ready
readinessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 10
# Liveness probe - ensure container stays healthy
livenessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
volumes:
- name: tmp-home
emptyDir: {} # Ephemeral - each instance gets fresh home
- name: shm
emptyDir:
medium: Memory
sizeLimit: 2Gi
---
# Ingress for the routing proxy (will be secured by Authentik)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: devcontainer-serverless-ingress
namespace: devcontainers
annotations:
# Authentik forward auth annotations
nginx.ingress.kubernetes.io/auth-url: http://authentik.authentik.svc.cluster.local/outpost.goauthentik.io/auth/nginx
nginx.ingress.kubernetes.io/auth-signin: https://auth.farh.net/outpost.goauthentik.io/start?rd=$escaped_request_uri
nginx.ingress.kubernetes.io/auth-response-headers: X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Name
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Host $http_host;
# SSL and general settings
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
# WebSocket support for VNC
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# Large file upload support (for file manager)
nginx.ingress.kubernetes.io/client-max-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
spec:
tls:
- hosts:
- devcontainer.farh.net
secretName: devcontainer-serverless-tls
rules:
- host: devcontainer.farh.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: devcontainer-routing-proxy
port:
number: 80
-107
View File
@@ -1,107 +0,0 @@
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: devcontainer-serverless
namespace: devcontainers
annotations:
# Scale to zero when not in use (saves resources)
autoscaling.knative.dev/minScale: "0"
autoscaling.knative.dev/maxScale: "10"
# Keep instances warm for 5 minutes after last request
autoscaling.knative.dev/scale-to-zero-grace-period: "5m"
# Target 1 concurrent request per pod (ensures isolation)
autoscaling.knative.dev/target: "1"
spec:
template:
metadata:
annotations:
# Container port for VNC web interface
autoscaling.knative.dev/targetPort: "5800"
# Timeout for cold starts (dev containers need time to initialize)
serving.knative.dev/timeoutSeconds: "300"
spec:
# Give containers more time to start (repo cloning + IDE launch)
timeoutSeconds: 300
containers:
- name: devcontainer
image: ghcr.io/cpfarhood/devcontainer:latest
ports:
- containerPort: 5800
name: vnc-web
env:
# Dynamic repo extraction will be handled by a startup script
- name: DYNAMIC_GITHUB_ROUTING
value: "true"
- name: IDE
value: "vscode"
- name: DISPLAY_WIDTH
value: "1920"
- name: DISPLAY_HEIGHT
value: "1080"
- name: SECURE_CONNECTION
value: "0"
- name: USER_ID
value: "1000"
- name: GROUP_ID
value: "1000"
# Enable file manager for easy upload/download
- name: WEB_FILE_MANAGER
value: "1"
- name: WEB_FILE_MANAGER_ALLOWED_PATHS
value: "/workspace,/config"
# Use secrets for sensitive data
envFrom:
- secretRef:
name: devcontainer-serverless-secrets
optional: true
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
volumeMounts:
- name: userhome
mountPath: /config
- name: shm
mountPath: /dev/shm
# Readiness probe - VNC must be ready
readinessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 3
# Liveness probe - ensure container stays healthy
livenessProbe:
httpGet:
path: /
port: 5800
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
volumes:
- name: userhome
emptyDir: {} # Ephemeral - each instance gets fresh home
- name: shm
emptyDir:
medium: Memory
sizeLimit: 2Gi
---
# Secret template for GitHub tokens, VNC passwords, etc.
apiVersion: v1
kind: Secret
metadata:
name: devcontainer-serverless-secrets
namespace: devcontainers
type: Opaque
data:
# Base64 encoded values - update as needed
# echo -n "your-github-token" | base64
GITHUB_TOKEN: ""
# echo -n "your-vnc-password" | base64
VNC_PASSWORD: ""
# echo -n "your-anthropic-key" | base64
ANTHROPIC_API_KEY: ""
-16
View File
@@ -1,16 +0,0 @@
# Lightweight routing proxy for dynamic GitHub repo routing
FROM nginx:1.27-alpine
# Install envsubst for template rendering
RUN apk add --no-cache gettext
# Copy nginx configuration template
COPY nginx.conf.template /etc/nginx/nginx.conf.template
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
-16
View File
@@ -1,16 +0,0 @@
#!/bin/sh
# Set default values for environment variables
DEVCONTAINER_SERVICE_URL=${DEVCONTAINER_SERVICE_URL:-"devcontainer-serverless.devcontainers.svc.cluster.local"}
# Create temp directories
mkdir -p /tmp/client_temp /tmp/proxy_temp /tmp/fastcgi_temp /tmp/uwsgi_temp /tmp/scgi_temp
# Substitute environment variables in nginx config
envsubst '$DEVCONTAINER_SERVICE_URL' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
echo "Starting routing proxy..."
echo "Routing to: $DEVCONTAINER_SERVICE_URL"
# Start nginx
exec "$@"
@@ -1,124 +0,0 @@
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'repo="$github_repo" user="$authentik_user"';
access_log /var/log/nginx/access.log main;
# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M; # Allow large file uploads via file manager
# Temp directories (writable in container)
client_body_temp_path /tmp/client_temp;
proxy_temp_path /tmp/proxy_temp;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
# Upstream Knative service (will be resolved by Knative networking)
upstream devcontainer_serverless {
server ${DEVCONTAINER_SERVICE_URL};
}
# Map to extract GitHub repo from URL path
map $request_uri $github_repo {
~^/github/([^/]+/[^/]+)(/.*)?$ https://github.com/$1;
default "";
}
# Extract Authentik user info from headers (set by Authentik forward auth)
map $http_x_authentik_username $authentik_user {
default $http_x_authentik_username;
}
server {
listen 8080;
server_name _;
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
# GitHub repo routing
location ~ ^/github/([^/]+/[^/]+)(/.*)?$ {
# Validate the repo format
if ($github_repo = "") {
return 400 "Invalid GitHub repository format. Use: /github/owner/repo\n";
}
# Log the routing decision
access_log /var/log/nginx/routing.log main;
# Set headers for the devcontainer
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Custom headers for dynamic repo routing
proxy_set_header X-GitHub-Repo $github_repo;
proxy_set_header X-Authentik-User $authentik_user;
proxy_set_header X-Request-Path $request_uri;
# Preserve Authentik auth headers
proxy_set_header X-Authentik-Username $http_x_authentik_username;
proxy_set_header X-Authentik-Email $http_x_authentik_email;
proxy_set_header X-Authentik-Name $http_x_authentik_name;
proxy_set_header X-Authentik-Groups $http_x_authentik_groups;
# Proxy settings for long-running connections (VNC)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 86400; # 24 hours
proxy_send_timeout 86400;
proxy_connect_timeout 30;
# Buffer settings for file uploads
proxy_buffering off;
proxy_request_buffering off;
# Forward to the devcontainer
proxy_pass http://devcontainer_serverless$2;
}
# Root path - show available repositories or redirect to auth
location = / {
return 200 "DevContainer Serverless\nUsage: /github/{owner}/{repo}\nExample: /github/microsoft/vscode\n";
add_header Content-Type text/plain;
}
# Anything else
location / {
return 404 "Not found. Use /github/{owner}/{repo} to access repositories.\n";
add_header Content-Type text/plain;
}
}
# WebSocket upgrade handling
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
}
-124
View File
@@ -1,124 +0,0 @@
#!/bin/bash
# Dynamic GitHub repository initialization for serverless mode
# This script extracts the GitHub repo from HTTP headers set by the routing proxy
set -e
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DYNAMIC-INIT: $*" >&2
}
log "Starting dynamic repository initialization..."
# In serverless mode, we expect the routing proxy to have set these environment variables
# from the HTTP headers. If running standalone, fallback to GITHUB_REPO env var.
if [[ "$SERVERLESS_MODE" == "true" ]]; then
log "Serverless mode detected"
# The routing proxy should have set these via HTTP headers -> env vars
# Check if we have the GitHub repo from the X-GitHub-Repo header
if [[ -n "$HTTP_X_GITHUB_REPO" ]]; then
GITHUB_REPO="$HTTP_X_GITHUB_REPO"
log "Using GitHub repo from header: $GITHUB_REPO"
elif [[ -n "$X_GITHUB_REPO" ]]; then
GITHUB_REPO="$X_GITHUB_REPO"
log "Using GitHub repo from X-GitHub-Repo: $GITHUB_REPO"
else
# Try to extract from a file written by an init container or sidecar
if [[ -f "/tmp/github-repo" ]]; then
GITHUB_REPO=$(cat /tmp/github-repo)
log "Using GitHub repo from file: $GITHUB_REPO"
else
log "ERROR: No GitHub repository specified in serverless mode"
log "Expected HTTP_X_GITHUB_REPO or X_GITHUB_REPO header from routing proxy"
exit 1
fi
fi
# Extract user info if available
if [[ -n "$HTTP_X_AUTHENTIK_USERNAME" ]]; then
export GIT_USER_NAME="${HTTP_X_AUTHENTIK_NAME:-$HTTP_X_AUTHENTIK_USERNAME}"
export GIT_USER_EMAIL="${HTTP_X_AUTHENTIK_EMAIL:-${HTTP_X_AUTHENTIK_USERNAME}@devcontainer.local}"
log "Using Authentik user: $GIT_USER_NAME <$GIT_USER_EMAIL>"
fi
else
log "Traditional mode - using GITHUB_REPO environment variable"
if [[ -z "$GITHUB_REPO" ]]; then
log "ERROR: GITHUB_REPO environment variable is required"
exit 1
fi
fi
# Validate the GitHub repo URL
if [[ ! "$GITHUB_REPO" =~ ^https://github\.com/[^/]+/[^/]+/?$ ]]; then
log "ERROR: Invalid GitHub repository URL: $GITHUB_REPO"
log "Expected format: https://github.com/owner/repo"
exit 1
fi
# Extract owner and repo name for workspace directory
REPO_OWNER=$(echo "$GITHUB_REPO" | sed 's|https://github.com/\([^/]*\)/.*|\1|')
REPO_NAME=$(echo "$GITHUB_REPO" | sed 's|https://github.com/[^/]*/\([^/]*\)/?|\1|')
WORKSPACE_DIR="/workspace/${REPO_OWNER}-${REPO_NAME}"
log "Repository: $GITHUB_REPO"
log "Owner: $REPO_OWNER"
log "Name: $REPO_NAME"
log "Workspace: $WORKSPACE_DIR"
# Configure git user (use defaults if not set via Authentik)
GIT_USER_NAME="${GIT_USER_NAME:-DevContainer User}"
GIT_USER_EMAIL="${GIT_USER_EMAIL:-devcontainer@example.com}"
log "Configuring git user: $GIT_USER_NAME <$GIT_USER_EMAIL>"
git config --global user.name "$GIT_USER_NAME"
git config --global user.email "$GIT_USER_EMAIL"
# Configure git credentials if GitHub token is available
if [[ -n "$GITHUB_TOKEN" ]]; then
log "Configuring GitHub credentials..."
git config --global credential.helper store
echo "https://oauth2:${GITHUB_TOKEN}@github.com" > ~/.git-credentials
chmod 600 ~/.git-credentials
else
log "No GitHub token provided - using public access only"
fi
# Create workspace directory
mkdir -p "$(dirname "$WORKSPACE_DIR")"
cd "$(dirname "$WORKSPACE_DIR")"
# Clone the repository
if [[ -d "$WORKSPACE_DIR" ]]; then
log "Repository directory exists, pulling latest changes..."
cd "$WORKSPACE_DIR"
git pull --ff-only || {
log "WARNING: Could not fast-forward, repository may have diverged"
log "Continuing with existing state..."
}
else
log "Cloning repository..."
git clone "$GITHUB_REPO" "$WORKSPACE_DIR" || {
log "ERROR: Failed to clone repository $GITHUB_REPO"
log "This may be a private repository or the URL may be incorrect"
exit 1
}
cd "$WORKSPACE_DIR"
fi
# Set the workspace directory for the IDE
export WORKSPACE_DIR
log "Repository initialization complete!"
log "Workspace directory: $WORKSPACE_DIR"
# Change to the workspace directory so the IDE opens in the right place
cd "$WORKSPACE_DIR"
# Export variables for the parent script
export GITHUB_REPO
export WORKSPACE_DIR
export REPO_OWNER
export REPO_NAME
-86
View File
@@ -1,86 +0,0 @@
#!/bin/bash
# Serverless-aware startup script for devcontainer
# This replaces the standard /startapp.sh when in serverless mode
set -e
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] SERVERLESS-START: $*" >&2
}
log "Starting serverless devcontainer..."
log "Mode: ${SERVERLESS_MODE:-traditional}"
log "IDE: ${IDE:-vscode}"
# Wait for HTTP headers to be available (in case of init container pattern)
# In Knative, the headers should be available immediately as env vars
sleep 2
# Check if we're in serverless mode with dynamic routing
if [[ "$SERVERLESS_MODE" == "true" && "$DYNAMIC_GITHUB_ROUTING" == "true" ]]; then
log "Dynamic GitHub routing enabled"
# In Knative, HTTP headers become environment variables with HTTP_ prefix
# But we also check for the unprefixed versions set by proxies
AVAILABLE_VARS=$(env | grep -E "(GITHUB|AUTHENTIK|X_)" | sort)
if [[ -n "$AVAILABLE_VARS" ]]; then
log "Available routing variables:"
echo "$AVAILABLE_VARS" | while read -r var; do
log " $var"
done
else
log "No routing variables found, checking for alternatives..."
# Check if there's a file with the repo info
if [[ -f "/tmp/github-repo" ]]; then
export GITHUB_REPO=$(cat /tmp/github-repo)
log "Found repo file: $GITHUB_REPO"
else
log "ERROR: No GitHub repository information available"
log "Expected routing headers or /tmp/github-repo file"
exit 1
fi
fi
# Use the dynamic initialization script
source /usr/local/bin/dynamic-init-repo
else
log "Using standard initialization..."
# Use the standard initialization
source /usr/local/bin/init-repo
fi
# At this point, WORKSPACE_DIR should be set by the init script
WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}"
log "Working directory: $WORKSPACE_DIR"
# Ensure we're in the workspace directory
cd "$WORKSPACE_DIR"
# Launch the appropriate IDE based on the IDE environment variable
case "${IDE:-vscode}" in
"vscode")
log "Starting VSCode..."
exec code --new-window --wait "$WORKSPACE_DIR"
;;
"antigravity")
log "Starting Antigravity..."
exec antigravity \
--no-sandbox \
--user-data-dir ~/.config/antigravity \
--disable-dev-shm-usage \
--disable-gpu \
--disable-features=VizDisplayCompositor \
--new-window \
"$WORKSPACE_DIR"
;;
"none")
log "No IDE requested, keeping container alive..."
exec sleep infinity
;;
*)
log "ERROR: Unknown IDE type: $IDE"
log "Valid options: vscode, antigravity, none"
exit 1
;;
esac