Skip to content

Commit 54fddaf

Browse files
MohsinHashmi-DataInnmohsin-wiserclaude
authored
docs: update devcontainer documentation with latest changes (#383)
* fix: suppress act() warnings and unhandled rejections in tests Configure testing-library for React 18+ and suppress harmless act() warnings that occur due to async state updates completing after test cleanup. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: upgrade postgresql from 16 to 18 Upgrade devcontainer database to PostgreSQL 18.1 (released Sept 2025). Key benefits: - 3x performance with new Async I/O subsystem - UUIDv7 function for timestamp-ordered UUIDs - Virtual generated columns - Skip scan on multicolumn B-tree indexes - OAuth 2.0 authentication support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add init to devcontainer to prevent zombie processes Adds `init: true` to the devcontainer service which uses tini as PID 1. This properly reaps zombie processes that accumulate from DevPod sessions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add container naming convention for devpod Adds instructions for configuring username-based container names when multiple developers share the same remote server. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: update proxy template with postgres 18 and init - Upgrade PostgreSQL from 16 to 18 - Add init: true to prevent zombie processes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: update devcontainer documentation with latest changes - Add PostgreSQL 18 and Redis 7 version info - Document zombie prevention with init: true - Add setup comparison table (DevPod vs Traefik) - Link between DevPod and multi-user setup guides - Update pre-installed tools versions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add auto-shutdown for idle devcontainers Adds idle-shutdown.sh script that automatically stops containers after 30 minutes of inactivity to conserve server resources. Features: - Detects activity via running processes (node, java, npm, etc.) - Checks for recent file modifications in workspace - Runs via cron every 5 minutes - Supports dry-run mode for testing - Configurable timeout duration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Mohsin Hashmi <mhashmi@wiser.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4d4d5d6 commit 54fddaf

3 files changed

Lines changed: 304 additions & 6 deletions

File tree

.devcontainer/proxy/README.md

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
This setup allows multiple developers to work on the same dev-server with isolated environments and shareable URLs.
44

5+
## Stack
6+
7+
Each user environment includes:
8+
9+
| Component | Version | Description |
10+
|-----------|---------|-------------|
11+
| Devcontainer | Latest | Pre-configured development environment |
12+
| PostgreSQL | **18** | Database server |
13+
| Redis | 7 | Cache and session storage |
14+
| Traefik | 3.0 | Reverse proxy for URL routing |
15+
16+
**Features:**
17+
- **Zombie-free containers** - Uses `init: true` (tini) to prevent zombie process accumulation
18+
- **Isolated databases** - Each user has their own PostgreSQL instance
19+
- **Shareable URLs** - Access via `username.server-ip.nip.io`
20+
- **Auto-shutdown** - Idle containers are automatically stopped after 30 minutes of inactivity
21+
522
## Quick Start
623

724
```bash
@@ -212,6 +229,7 @@ Then point client DNS to the dev server.
212229
| `docker-compose.<user>.yml` | Generated user-specific config |
213230
| `setup-user.sh` | User setup script |
214231
| `generate-hosts.sh` | DNS helper for /etc/hosts |
232+
| `idle-shutdown.sh` | Auto-shutdown idle containers |
215233

216234
## How It Works
217235

@@ -361,13 +379,76 @@ command:
361379
- '--certificatesresolvers.myresolver.acme.tlschallenge=true'
362380
```
363381

364-
## Comparison with Single-User Setup
382+
## Auto-Shutdown (Idle Timeout)
383+
384+
To conserve server resources, containers are automatically stopped after 30 minutes of inactivity.
385+
386+
### What counts as activity?
387+
388+
- Running development processes (node, java, npm, mvn, vite, webpack, etc.)
389+
- Recent file modifications in the workspace (within last 5 minutes)
390+
391+
### How it works
392+
393+
A cron job runs every 5 minutes and checks each container:
394+
395+
```bash
396+
# Check runs every 5 minutes
397+
*/5 * * * * /home/mohsin/idle-shutdown.sh 30
398+
```
399+
400+
### Manual commands
401+
402+
```bash
403+
# Check status without stopping (dry-run)
404+
./idle-shutdown.sh --dry-run
405+
406+
# Use custom timeout (60 minutes)
407+
./idle-shutdown.sh 60
408+
409+
# View shutdown logs
410+
tail -f /var/log/idle-shutdown.log
411+
```
412+
413+
### Restart after shutdown
365414

366-
| Aspect | Single-User | Multi-User |
415+
If your container was stopped due to inactivity, simply restart it:
416+
417+
```bash
418+
cd .devcontainer/proxy
419+
docker compose -f docker-compose.<username>.yml start
420+
```
421+
422+
Or run the full setup again:
423+
424+
```bash
425+
./setup-user.sh <username>
426+
```
427+
428+
### Disable auto-shutdown for a user
429+
430+
To keep a container running indefinitely, add a marker file:
431+
432+
```bash
433+
docker exec dev-<username> touch /tmp/.keep-alive
434+
```
435+
436+
Then modify the idle-shutdown.sh script to check for this file.
437+
438+
## Comparison with DevPod Setup
439+
440+
| Aspect | DevPod (Single-User) | Traefik (Multi-User) |
367441
| ---------------- | -------------------------- | ----------------------------- |
368442
| **Isolation** | Shared volumes | Isolated per user |
369443
| **URLs** | `localhost:3000` | `alice.192-168-1-100.nip.io` |
370444
| **Port Conflicts** | Yes, if multiple users | No, Traefik handles routing |
371445
| **Shareable** | No (localhost only) | Yes, anyone can access |
372-
| **Setup** | VS Code devcontainer | `./setup-user.sh <name>` |
446+
| **Setup** | `devpod up ...` | `./setup-user.sh <name>` |
447+
| **IDE Support** | VS Code, Cursor, JetBrains | VS Code (attach to container) |
373448
| **Best For** | Local dev, solo work | Team collaboration |
449+
450+
**When to use which:**
451+
- **DevPod**: You're working locally or need full IDE integration
452+
- **Traefik Multi-User**: Multiple developers sharing a remote server, need shareable URLs
453+
454+
See [DevPod Setup Guide](../../docs/DEVPOD_SETUP.md) for single-user setup instructions.
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#!/bin/bash
2+
# =============================================================================
3+
# Idle Container Shutdown Script
4+
# =============================================================================
5+
# Stops devcontainers that have been idle for a specified duration.
6+
#
7+
# Usage:
8+
# ./idle-shutdown.sh # Use default 30 minute timeout
9+
# ./idle-shutdown.sh 60 # Use 60 minute timeout
10+
# ./idle-shutdown.sh --dry-run # Show what would be stopped without stopping
11+
#
12+
# Install as cron job:
13+
# */5 * * * * /path/to/idle-shutdown.sh >> /var/log/idle-shutdown.log 2>&1
14+
# =============================================================================
15+
16+
set -e
17+
18+
# Configuration
19+
IDLE_TIMEOUT_MINUTES="${1:-30}"
20+
DRY_RUN=false
21+
ACTIVITY_DIR="/tmp/devcontainer-activity"
22+
LOG_PREFIX="[idle-shutdown]"
23+
24+
# Check for dry-run flag
25+
if [[ "$1" == "--dry-run" ]]; then
26+
DRY_RUN=true
27+
IDLE_TIMEOUT_MINUTES="${2:-30}"
28+
fi
29+
30+
# Colors for output
31+
RED='\033[0;31m'
32+
GREEN='\033[0;32m'
33+
YELLOW='\033[1;33m'
34+
BLUE='\033[0;34m'
35+
NC='\033[0m'
36+
37+
log_info() {
38+
echo -e "${LOG_PREFIX} $(date '+%Y-%m-%d %H:%M:%S') ${BLUE}INFO${NC} $1"
39+
}
40+
41+
log_warn() {
42+
echo -e "${LOG_PREFIX} $(date '+%Y-%m-%d %H:%M:%S') ${YELLOW}WARN${NC} $1"
43+
}
44+
45+
log_success() {
46+
echo -e "${LOG_PREFIX} $(date '+%Y-%m-%d %H:%M:%S') ${GREEN}OK${NC} $1"
47+
}
48+
49+
log_error() {
50+
echo -e "${LOG_PREFIX} $(date '+%Y-%m-%d %H:%M:%S') ${RED}ERROR${NC} $1"
51+
}
52+
53+
# Create activity tracking directory
54+
mkdir -p "$ACTIVITY_DIR"
55+
56+
# Processes that indicate activity (development work in progress)
57+
ACTIVE_PROCESSES="node|java|npm|mvn|spring|vite|webpack|esbuild|tsc|python|gradle|code-server"
58+
59+
# Get all running devcontainers (exclude proxy and system containers)
60+
get_devcontainers() {
61+
docker ps --format '{{.Names}}' | grep -E '^dev-' | grep -v 'dev-proxy' || true
62+
}
63+
64+
# Check if a container has active development processes
65+
is_container_active() {
66+
local container="$1"
67+
68+
# Check for active development processes
69+
local active_procs
70+
active_procs=$(docker exec "$container" ps aux 2>/dev/null | grep -E "$ACTIVE_PROCESSES" | grep -v grep | wc -l || echo "0")
71+
72+
if [[ "$active_procs" -gt 0 ]]; then
73+
return 0 # Active
74+
fi
75+
76+
# Check for recent file modifications in workspace (last 5 minutes)
77+
local recent_changes
78+
recent_changes=$(docker exec "$container" find /workspaces -type f \( -name "*.java" -o -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) -mmin -5 2>/dev/null | head -1 || true)
79+
80+
if [[ -n "$recent_changes" ]]; then
81+
return 0 # Active
82+
fi
83+
84+
return 1 # Idle
85+
}
86+
87+
# Get the username from container name (dev-username -> username)
88+
get_username() {
89+
local container="$1"
90+
echo "$container" | sed 's/^dev-//'
91+
}
92+
93+
# Update last activity timestamp
94+
update_activity() {
95+
local container="$1"
96+
local activity_file="$ACTIVITY_DIR/$container"
97+
date +%s > "$activity_file"
98+
}
99+
100+
# Get last activity timestamp
101+
get_last_activity() {
102+
local container="$1"
103+
local activity_file="$ACTIVITY_DIR/$container"
104+
105+
if [[ -f "$activity_file" ]]; then
106+
cat "$activity_file"
107+
else
108+
# First time seeing this container, record now
109+
update_activity "$container"
110+
date +%s
111+
fi
112+
}
113+
114+
# Calculate idle time in minutes
115+
get_idle_minutes() {
116+
local container="$1"
117+
local last_activity
118+
last_activity=$(get_last_activity "$container")
119+
local now
120+
now=$(date +%s)
121+
local idle_seconds=$((now - last_activity))
122+
echo $((idle_seconds / 60))
123+
}
124+
125+
# Stop a container and its associated services
126+
stop_container_stack() {
127+
local container="$1"
128+
local username
129+
username=$(get_username "$container")
130+
131+
log_warn "Stopping idle container stack for: $username"
132+
133+
# Find the compose file
134+
local compose_file
135+
for dir in /home/*/; do
136+
if [[ -f "${dir}.devpod/agent/contexts/default/workspaces/simpleaccounts-uae/content/.devcontainer/proxy/docker-compose.${username}.yml" ]]; then
137+
compose_file="${dir}.devpod/agent/contexts/default/workspaces/simpleaccounts-uae/content/.devcontainer/proxy/docker-compose.${username}.yml"
138+
break
139+
fi
140+
done
141+
142+
if [[ -n "$compose_file" && -f "$compose_file" ]]; then
143+
if [[ "$DRY_RUN" == "true" ]]; then
144+
log_info "[DRY-RUN] Would run: docker compose -f $compose_file stop"
145+
else
146+
docker compose -f "$compose_file" stop 2>/dev/null || true
147+
log_success "Stopped container stack for: $username"
148+
fi
149+
else
150+
# Fallback: stop individual containers
151+
if [[ "$DRY_RUN" == "true" ]]; then
152+
log_info "[DRY-RUN] Would stop: $container, db-$username, redis-$username"
153+
else
154+
docker stop "$container" "db-$username" "redis-$username" 2>/dev/null || true
155+
log_success "Stopped containers for: $username"
156+
fi
157+
fi
158+
159+
# Clean up activity file
160+
rm -f "$ACTIVITY_DIR/$container"
161+
}
162+
163+
# Main logic
164+
main() {
165+
log_info "Starting idle container check (timeout: ${IDLE_TIMEOUT_MINUTES} minutes)"
166+
167+
local containers
168+
containers=$(get_devcontainers)
169+
170+
if [[ -z "$containers" ]]; then
171+
log_info "No devcontainers running"
172+
exit 0
173+
fi
174+
175+
local stopped_count=0
176+
local active_count=0
177+
178+
for container in $containers; do
179+
local username
180+
username=$(get_username "$container")
181+
182+
if is_container_active "$container"; then
183+
update_activity "$container"
184+
log_info "Container $container is ACTIVE (user: $username)"
185+
((active_count++))
186+
else
187+
local idle_minutes
188+
idle_minutes=$(get_idle_minutes "$container")
189+
190+
if [[ "$idle_minutes" -ge "$IDLE_TIMEOUT_MINUTES" ]]; then
191+
log_warn "Container $container is IDLE for $idle_minutes minutes (user: $username)"
192+
stop_container_stack "$container"
193+
((stopped_count++))
194+
else
195+
local remaining=$((IDLE_TIMEOUT_MINUTES - idle_minutes))
196+
log_info "Container $container idle for $idle_minutes min, will stop in $remaining min (user: $username)"
197+
fi
198+
fi
199+
done
200+
201+
log_info "Summary: $active_count active, $stopped_count stopped"
202+
}
203+
204+
# Run main function
205+
main "$@"

docs/DEVPOD_SETUP.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,22 @@ This guide explains how to set up your development environment using [DevPod](ht
66

77
DevPod creates reproducible development environments using containers. Benefits:
88

9-
- **Zero configuration** - Everything is pre-configured (Java 21, Node 20, PostgreSQL, Redis)
9+
- **Zero configuration** - Everything is pre-configured (Java 21, Node 20, PostgreSQL 18, Redis 7)
1010
- **Fast setup** - Prebuilt images mean you're coding in under a minute
1111
- **Consistent** - Same environment for all developers
1212
- **IDE agnostic** - Works with VS Code, JetBrains IDEs, Cursor, or SSH
13+
- **Zombie-free** - Uses tini init system to prevent zombie processes
14+
15+
## Choose Your Setup
16+
17+
| Setup | Best For | Access Method |
18+
|-------|----------|---------------|
19+
| **DevPod (Single User)** | Local development, solo work | `localhost:3000` via port forwarding |
20+
| **Traefik Multi-User** | Team collaboration, shared server | `username.server-ip.nip.io` via reverse proxy |
21+
22+
**Quick decision:**
23+
- Working alone or locally? → Use DevPod (this guide)
24+
- Sharing a dev server with teammates? → Use [Multi-User Setup](./../.devcontainer/proxy/README.md)
1325

1426
## Prerequisites
1527

@@ -171,8 +183,8 @@ The dev container includes:
171183
| Node.js | 20 |
172184
| npm | Latest |
173185
| Maven | Via wrapper |
174-
| PostgreSQL Client | Latest |
175-
| Redis CLI | Latest |
186+
| PostgreSQL | 18 |
187+
| Redis | 7 |
176188
| Git | Latest |
177189
| GitHub CLI | Latest |
178190
| Docker-in-Docker | Latest |

0 commit comments

Comments
 (0)