Skip to content

Commit 99d7faf

Browse files
MohsinHashmi-DataInnmohsin-wiserclaude
authored
fix: static volume names and external proxy access for devcontainer (#390)
* 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> * fix: use static volume names for docker compose compatibility Docker Compose doesn't support variable interpolation in volume name keys. Use static names - Docker Compose will prefix them with the project name automatically for isolation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: update volumes section for static naming Update documentation to reflect that Docker Compose uses static volume names (postgres-data, redis-data, etc.) instead of variable-interpolated names. Docker Compose automatically prefixes these with the project name for isolation between projects. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use volume name property for user isolation Use the 'name:' property within volume definitions instead of variable interpolation in volume keys. This provides: - Valid Docker Compose syntax (keys must be static) - User-specific volume names (e.g., alice-postgres-data) - Proper isolation between users on shared Docker hosts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: configure traefik network and vite for external proxy access - add traefik.docker.network label to docker-compose.yml to fix 504 errors when container is on multiple networks - add allowedHosts: true to vite.config.js to allow external proxy access - document SSH auth failures, Traefik routing, and Vite host issues in LESSONS_LEARNED.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: update package-lock.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: restrict vite allowed hosts to specific domains for security 🤖 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 49fd8c9 commit 99d7faf

4 files changed

Lines changed: 251 additions & 15 deletions

File tree

.devcontainer/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ services:
6969
# Traefik labels for external routing (active when connected to dev-proxy-network)
7070
labels:
7171
- 'traefik.enable=true'
72+
- 'traefik.docker.network=dev-proxy-network'
7273
# Frontend routing (port 3000)
7374
- 'traefik.http.routers.${USER:-devuser}-frontend.rule=HostRegexp(`${USER:-devuser}.{ip:[0-9-]+}.nip.io`)'
7475
- 'traefik.http.routers.${USER:-devuser}-frontend.entrypoints=web'

apps/frontend/vite.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export default defineConfig({
113113
host: true, // Listen on all interfaces (0.0.0.0 and ::)
114114
open: false, // Don't auto-open browser
115115
strictPort: false, // Allow fallback to next available port if 3000 is taken
116+
allowedHosts: ['localhost', '.nip.io', '.dev.simpleaccounts.local'], // Restrict to known hosts for security
116117
// Reduce memory usage in dev
117118
fs: {
118119
// Limit file system access

docs/LESSONS_LEARNED.md

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ This document captures important lessons learned during development to prevent r
1010
2. [Apache POI / Java 17 Compatibility](#2-apache-poi--java-17-compatibility)
1111
3. [SonarQube SSL Configuration in Coolify](#3-sonarqube-ssl-configuration-in-coolify)
1212
4. [Java Dependency Version Validity & API Stability](#4-java-dependency-version-validity--api-stability)
13+
5. [DevPod SSH Authentication Failures](#5-devpod-ssh-authentication-failures)
14+
6. [Traefik Multi-Network Container Routing](#6-traefik-multi-network-container-routing)
15+
7. [Vite External Host Access (403 Forbidden)](#7-vite-external-host-access-403-forbidden)
1316

1417
---
1518

@@ -190,4 +193,250 @@ java.lang.NoSuchMethodError: org.apache.commons.csv.CSVFormat.withFirstRecordAsH
190193

191194
---
192195

196+
## 5. DevPod SSH Authentication Failures
197+
198+
**Date:** December 2025
199+
200+
**Issue:** DevPod workspace creation gets stuck indefinitely on "Waiting for devpod agent to come up..." and eventually fails with SSH authentication errors.
201+
202+
**Root Cause:**
203+
Multiple SSH keys loaded in the ssh-agent cause the SSH server to reject the connection before finding the correct key. The server's `MaxAuthTries` limit (typically 6) is exceeded when DevPod tries each key in the agent sequentially.
204+
205+
**Error Messages:**
206+
207+
```
208+
Too many authentication failures
209+
Disconnected from 65.108.51.136 port 22
210+
211+
# Or after clearing agent:
212+
moshinhashmi@65.108.51.136: Permission denied (publickey,password).
213+
```
214+
215+
**Symptoms:**
216+
217+
- DevPod repeatedly shows "Waiting for devpod agent to come up..."
218+
- Debug mode (`--debug`) reveals "Too many authentication failures"
219+
- SSH works fine from terminal but DevPod fails
220+
- The issue persists even after configuring `IdentitiesOnly yes` in SSH config
221+
222+
**Technical Details:**
223+
224+
1. DevPod by default adds ALL private keys from `~/.ssh/` to the ssh-agent before connecting
225+
2. Even with `IdentitiesOnly yes` in SSH config, the ssh-agent keys are tried first
226+
3. With 6+ keys in the agent, the server rejects before the correct key is tried
227+
4. DevPod also needs SSH agent forwarding enabled for git operations on the remote
228+
229+
**Solution:**
230+
231+
1. **Disable DevPod's automatic key loading:**
232+
```bash
233+
devpod context set-options -o SSH_ADD_PRIVATE_KEYS=false
234+
```
235+
236+
2. **Add `ForwardAgent yes` to SSH config for git credential forwarding:**
237+
```
238+
Host dev-server
239+
HostName 65.108.51.136
240+
User mohsin
241+
IdentityFile ~/.ssh/id_ed25519
242+
IdentitiesOnly yes
243+
ForwardAgent yes
244+
ServerAliveInterval 30
245+
ServerAliveCountMax 3
246+
TCPKeepAlive yes
247+
```
248+
249+
3. **Manually manage ssh-agent keys - load only required keys:**
250+
```bash
251+
# Clear all keys
252+
ssh-add -D
253+
254+
# Add only the key for the dev server
255+
ssh-add ~/.ssh/id_ed25519
256+
257+
# Add GitHub key for git operations (will be forwarded)
258+
ssh-add ~/.ssh/id_rsa_personal
259+
260+
# Verify only 2 keys are loaded
261+
ssh-add -l
262+
```
263+
264+
4. **Clean up and recreate the workspace:**
265+
```bash
266+
# Delete the stuck workspace
267+
devpod delete <workspace-name> --force
268+
269+
# Clean up remote containers if needed
270+
ssh dev-server "docker ps -a --format '{{.Names}}' | xargs -r docker rm -f"
271+
272+
# Pull latest image
273+
ssh dev-server "docker pull ghcr.io/simpleaccounts/simpleaccounts-uae-devcontainer:latest"
274+
275+
# Create fresh workspace from current directory
276+
devpod up . --provider ssh --ide cursor --id simpleaccounts-uae
277+
```
278+
279+
**Prevention:**
280+
281+
1. Always set `SSH_ADD_PRIVATE_KEYS=false` in DevPod context when using multiple SSH keys
282+
2. Configure `ForwardAgent yes` in SSH config for hosts where you need git access
283+
3. Keep only necessary keys in ssh-agent (2-3 max)
284+
4. Use explicit `IdentityFile` and `IdentitiesOnly yes` in SSH config
285+
5. When DevPod hangs on agent startup, use `--debug` flag to see the actual error
286+
287+
**DevPod Configuration Reference:**
288+
289+
```bash
290+
# View current SSH provider options
291+
devpod provider options ssh
292+
293+
# Update SSH provider with specific flags (if needed)
294+
devpod provider update ssh -o EXTRA_FLAGS="-o IdentitiesOnly=yes"
295+
296+
# List workspaces
297+
devpod list
298+
299+
# Delete workspace with force
300+
devpod delete <name> --force
301+
```
302+
303+
**Related Commands:**
304+
305+
```bash
306+
# Check ssh-agent keys
307+
ssh-add -l
308+
309+
# Clear all keys from agent
310+
ssh-add -D
311+
312+
# Add specific key
313+
ssh-add ~/.ssh/id_ed25519
314+
315+
# Test SSH connection with verbose output
316+
ssh -v dev-server "echo OK"
317+
318+
# Test SSH agent forwarding
319+
ssh -A dev-server "ssh -T git@github.com"
320+
```
321+
322+
---
323+
324+
## 6. Traefik Multi-Network Container Routing
325+
326+
**Date:** December 2025
327+
328+
**Issue:** External URLs via Traefik return 504 Gateway Timeout, even though services are running inside the container and Traefik shows them as "UP".
329+
330+
**Root Cause:**
331+
When a Docker container is connected to multiple networks, Traefik may pick the wrong network IP to route traffic. Traefik selects the first network alphabetically unless explicitly configured.
332+
333+
**Symptoms:**
334+
335+
- Traefik API shows services as "UP" with an IP address
336+
- Requests via Traefik timeout (504 Gateway Timeout)
337+
- Direct container access works fine
338+
- Container is on multiple Docker networks
339+
340+
**Example Scenario:**
341+
```
342+
Container dev-mohsin:
343+
- mohsin-internal: 172.18.0.4 (internal network for db/redis)
344+
- dev-proxy-network: 172.19.0.3 (Traefik's network)
345+
346+
Traefik incorrectly tries to route to 172.18.0.4 (wrong network)
347+
```
348+
349+
**Solution:**
350+
351+
Add the `traefik.docker.network` label to specify which network Traefik should use:
352+
353+
```yaml
354+
# docker-compose.yml
355+
services:
356+
devcontainer:
357+
labels:
358+
- 'traefik.enable=true'
359+
- 'traefik.docker.network=dev-proxy-network' # Add this line!
360+
- 'traefik.http.routers.myapp.rule=Host(`myapp.example.com`)'
361+
- 'traefik.http.services.myapp.loadbalancer.server.port=3000'
362+
```
363+
364+
After adding the label, restart both the container and Traefik:
365+
```bash
366+
docker restart my-container
367+
docker restart traefik
368+
```
369+
370+
**Prevention:**
371+
372+
1. Always add `traefik.docker.network` label when containers are on multiple networks
373+
2. Keep the Traefik proxy network consistent across all services
374+
3. Verify the correct IP is shown in Traefik dashboard after changes
375+
4. When debugging, check `docker inspect <container>` to see all network IPs
376+
377+
**Verification:**
378+
```bash
379+
# Check Traefik is using correct IP
380+
curl -s http://localhost:8090/api/http/services | python3 -c \
381+
'import json,sys; [print(s["name"], s.get("serverStatus",{})) for s in json.load(sys.stdin)]'
382+
383+
# Should show the dev-proxy-network IP, not internal network IP
384+
```
385+
386+
---
387+
388+
## 7. Vite External Host Access (403 Forbidden)
389+
390+
**Date:** December 2025
391+
392+
**Issue:** Vite dev server returns 403 Forbidden when accessed via external proxy (Traefik) or non-localhost hostname.
393+
394+
**Root Cause:**
395+
Vite 5+ has stricter security defaults that reject requests from unknown hosts. Even with `--host 0.0.0.0`, Vite validates the `Host` header and blocks requests that don't match localhost or allowed patterns.
396+
397+
**Error Message:**
398+
399+
- HTTP 403 Forbidden response
400+
- No error in Vite logs (silent rejection)
401+
402+
**Symptoms:**
403+
404+
- `curl localhost:3000` works
405+
- `curl external-url.nip.io` returns 403
406+
- No CORS errors (not a CORS issue)
407+
- Traefik shows service as UP
408+
409+
**Solution:**
410+
411+
Add `allowedHosts` with a restricted allowlist to the Vite server configuration:
412+
413+
```javascript
414+
// vite.config.js
415+
export default defineConfig({
416+
server: {
417+
port: 3000,
418+
host: true, // Listen on all interfaces
419+
allowedHosts: ['localhost', '.nip.io', '.dev.simpleaccounts.local'], // Restrict to known hosts
420+
},
421+
});
422+
```
423+
424+
**Security Note:** Avoid using `allowedHosts: true` as it disables Vite's host-header check, which mitigates DNS rebinding attacks. Always use a specific allowlist.
425+
426+
**Common patterns:**
427+
- `.nip.io` - for dynamic IP-based URLs (e.g., `user.65-108-51-136.nip.io`)
428+
- `.dev.simpleaccounts.local` - for local domain routing
429+
- `localhost` - for local development
430+
431+
**Prevention:**
432+
433+
1. When setting up proxy access, always configure `allowedHosts` in Vite
434+
2. Use wildcard patterns for dynamic subdomains (e.g., `.nip.io`)
435+
3. Document the required Vite configuration for external access
436+
4. Test with the actual external URL during development setup
437+
438+
**Note:** This is a security feature. In production, Vite's dev server should not be exposed - use a proper build instead.
439+
440+
---
441+
193442
_Add new lessons learned above this line, following the same format._

package-lock.json

Lines changed: 0 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)