Moving it to /workspace (emptyDir) wiped Happy Coder's auth, config, and state on every pod restart. The daemon also failed to start on boot because the settings were gone. Keep HAPPY_HOME_DIR on the home PVC (/home/user/.happy) for persistence. The stale lock cleanup in init-repo.sh already handles the daemon.state.json.lock problem that motivated the workspace move. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Antigravity Dev Container
A containerized cloud development environment with web-based GUI access, featuring:
- VSCode via browser-based VNC (port 5800)
- Happy Coder AI assistant backed by Claude
- Automatic GitHub repo cloning on startup
- Persistent home directory via ReadWriteMany PVC
- Kubernetes-native Helm chart deployment
Quick Start
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 |
kubectl create secret generic devcontainer-mydev-secrets-env \
--from-literal=GITHUB_TOKEN='ghp_...' \
--from-literal=VNC_PASSWORD='changeme'
Or use SealedSecrets:
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
helm install mydev ./chart \
--set name=mydev \
--set githubRepo=https://github.com/youruser/yourrepo
3. Access
# 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:
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
Core values
| Value | Default | Description |
|---|---|---|
name |
"" |
Instance name — used in all resource names (devcontainer-{name}) |
githubRepo |
"" |
Repository to clone into /workspace on startup |
image.repository |
ghcr.io/cpfarhood/devcontainer |
Container image |
image.tag |
latest |
Image tag |
Happy Coder
| Value | Default | Description |
|---|---|---|
happyServerUrl |
https://happy.farh.net |
Happy Coder server endpoint |
happyWebappUrl |
https://happy-coder.farh.net |
Happy Coder webapp URL |
happyHomeDir |
/workspace/.happy |
Happy runtime state directory (ephemeral — lives in emptyDir) |
happyExperimental |
true |
Enable experimental Happy features |
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 | * |
# 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.
Display and resources
| Value | Default | Description |
|---|---|---|
display.width |
1920 |
VNC width (px) |
display.height |
1080 |
VNC height (px) |
secureConnection |
0 |
Set to 1 if TLS is not terminated upstream |
userId |
1000 |
UID for the app user |
groupId |
1000 |
GID for the app user |
storage.size |
32Gi |
Home PVC size |
storage.className |
ceph-filesystem |
StorageClass (must be ReadWriteMany) |
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 (as app user, UID 1000)
→ cont-init.d/20-fix-user-shell.sh — fix shell/home on baseimage-gui app user
→ /startapp.sh
→ init-repo.sh
→ clone / pull GITHUB_REPO into /workspace/{repo}
→ happy daemon start — starts Happy Coder background daemon
→ code --new-window /workspace/{repo} — opens VSCode in VNC
Storage
| Mount | Source | Persistence |
|---|---|---|
/home |
ReadWriteMany PVC (userhome-{name}) |
Survives pod restarts — stores Claude credentials, dotfiles, git config |
/workspace |
emptyDir |
Ephemeral — repo is re-cloned on each pod start |
Happy Coder's runtime state (HAPPY_HOME_DIR) is kept in /workspace/.happy so stale lock files never survive a pod restart.
Troubleshooting
Happy Coder daemon not starting
# Check daemon status
happy daemon status
# Start manually (also clears any stale lock)
happy daemon start
# View daemon logs
ls ~/.happy/logs/ || ls /workspace/.happy/logs/
Claude not authenticated
Browser-based OAuth login is the primary method (works inside VNC via the Chrome wrapper). If you prefer API key auth:
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
kubectl port-forward deployment/devcontainer-mydev 5800:5800
kubectl logs deployment/devcontainer-mydev
kubectl describe pod -l instance=mydev
Repository not cloning
kubectl logs deployment/devcontainer-mydev | grep "Repository Initialization"
kubectl exec deployment/devcontainer-mydev -- env | grep GITHUB
Local Docker run
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
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
- AI assistant: Happy Coder + Claude