Portable IDE. Vibe Code Anywhere.
A self-hosted web IDE that lets you code from anywhere — especially from your mobile device. Deploy it on your server and access your development environment from any browser.
- Monaco Editor — The same editor that powers VS Code
- Multi-file tabs with unsaved changes indicator
- Syntax highlighting for 20+ languages
- Auto-save with Ctrl/Cmd+S
- Full PTY Terminal — Real terminal, not emulation
- WebSocket-based real-time streaming
- Clickable links in output
- 256-color and truecolor support
- Native Claude CLI support built-in
- Dedicated Claude terminal panel
- Virtual keyboard for mobile devices
- AI-powered coding assistance
- Multi-repository support — Work with multiple git repos in one project
- Stage, unstage, commit, push, and pull
- Visual diff viewer with color-coded changes
- Branch management and commit history
- Undo last commit (soft reset)
- GitHub token management for private repos
- Tree-style navigation
- Create, rename, and delete files/folders
- File type icons
- Smart filtering (hides node_modules, .git, etc.)
- Two-Factor Authentication — TOTP-based (Google Authenticator, Authy)
- JWT with HttpOnly cookies
- Account lockout after failed attempts
- Rate limiting on auth endpoints
- Path traversal protection
- Responsive layout for all screen sizes
- Virtual keyboard for terminal input
- Touch-friendly UI elements
- Collapsible panels
- Docker and Docker Compose
- A server with a public IP (or run locally)
- Clone the repository:
git clone https://github.com/guneyural/nomad-ide.git
cd nomad-ide- Configure environment:
cp .env.example .env
# Generate a secure JWT secret
echo "JWT_SECRET=$(openssl rand -base64 32)" >> .env
- Start the application:
docker-compose up -d- Access the IDE:
- Open
http://your-server-ip:3000 - Complete initial setup (create admin account + enable 2FA)
- Open
- Next.js 16 with Turbopack
- React 19
- Tailwind CSS for styling
- Monaco Editor for code editing
- xterm.js for terminal
- Redux Toolkit + RTK Query
- Express.js REST API
- Socket.io for real-time communication
- Prisma ORM with SQLite
- node-pty for terminal sessions
- pnpm workspaces (monorepo)
- TypeScript throughout
- Docker for deployment
nomad-ide/
├── apps/
│ ├── web/ # Next.js frontend
│ │ ├── src/
│ │ │ ├── app/ # App router pages
│ │ │ ├── components/ # React components
│ │ │ ├── hooks/ # Custom hooks
│ │ │ ├── lib/ # Utilities
│ │ │ └── store/ # Redux store
│ │ └── ...
│ │
│ └── server/ # Express backend
│ ├── src/
│ │ ├── routes/ # API routes
│ │ ├── services/ # Business logic
│ │ ├── middleware/ # Auth, validation
│ │ └── utils/ # Helpers
│ ├── prisma/ # Database schema
│ └── ...
│
├── packages/
│ └── shared/ # Shared types & utils
│
├── docker-compose.yml
├── Dockerfile
└── README.md
| Variable | Description | Required | Default |
|---|---|---|---|
JWT_SECRET |
Secret for JWT signing | Yes | — |
DATABASE_URL |
SQLite database path | No | file:./nomad-ide.db |
PORT |
Backend server port | No | 4000 |
CORS_ORIGIN |
Frontend URL for CORS | No | http://localhost:3000 |
COOKIE_SECURE |
Set to false for HTTP (see below) |
No | true in production |
PROJECTS_DIR |
Base projects directory | No | ~/projects |
⚠️ Important: HTTP vs HTTPSBy default, cookies are set with
Secureflag in production, which only works over HTTPS. If you're running without HTTPS (e.g.,http://your-ip:3000), you must set:COOKIE_SECURE=falseOtherwise, authentication will fail after login (cookies won't be saved by the browser).
docker-compose up -dData is persisted in the ./data directory. Projects are mounted from ~/projects.
If you prefer docker run instead of docker-compose:
# 1. Create data directory and generate JWT secret
mkdir -p ~/nomad-ide-data
openssl rand -base64 32 > ~/nomad-ide-data/jwt-secret.txt
chmod 600 ~/nomad-ide-data/jwt-secret.txt
# 2. Create the projects directory with correct permissions
sudo mkdir -p /home/nomad
sudo chown -R 1001:1001 /home/nomad
# 3. Pull and run the container
docker run -d \
--name nomad-ide \
-p 3005:3000 \
-p 3006:4000 \
-v ~/nomad-ide-data:/app/data \
-v /home/nomad:/home/nomad \
-e JWT_SECRET="$(cat ~/nomad-ide-data/jwt-secret.txt)" \
-e COOKIE_SECURE=false \
--restart unless-stopped \
guneyural0/nomad-ide:latestThen access the IDE at http://your-server-ip:3005
📁 Volume Mounts Explained:
-v ~/nomad-ide-data:/app/data— Database and app data (persists between updates)-v /home/nomad:/home/nomad— Projects directory (your code files)
⚠️ Important: PermissionsThe container runs as user
nomad(uid 1001). You must set correct ownership on the projects directory:sudo chown -R 1001:1001 /home/nomadWithout this, you'll get "Permission denied" errors when running
npm install,git, or Claude CLI.
🔌 Port Mapping:
- Frontend: External port
3005→ Container port3000- Backend/WebSocket: External port
3006→ Container port4000The frontend automatically connects to backend on
frontend_port + 1. So if you use-p 8080:3000 -p 8081:4000, access viahttp://your-ip:8080.
🔑 Environment Variables:
JWT_SECRET— Required. Keep this consistent across container updates to preserve sessions.COOKIE_SECURE=false— Required for HTTP. Remove this if using HTTPS.
For HTTPS support, use a reverse proxy:
server {
listen 443 ssl http2;
server_name ide.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Frontend
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Backend API
location /api {
proxy_pass http://localhost:4000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# WebSocket
location /socket.io {
proxy_pass http://localhost:4000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}ide.yourdomain.com {
reverse_proxy /api/* localhost:4000
reverse_proxy /socket.io/* localhost:4000
reverse_proxy * localhost:3000
}
- Open a project in Nomad IDE
- Go to Settings (gear icon) and enter your Anthropic API key
- Click the Claude tab in the bottom panel
- Start coding with AI assistance
Note: Your API key is stored securely and never leaves your server.
The Claude terminal includes a virtual keyboard for mobile devices.
| Feature | Description |
|---|---|
| 2FA (TOTP) | Google Authenticator, Authy compatible |
| JWT + HttpOnly Cookies | Secure session management |
| Account Lockout | 15 min lockout after 5 failed attempts |
| Rate Limiting | 10 requests per 15 min on auth |
| Path Traversal Protection | Can't access files outside project |
| Security Headers | Helmet.js middleware |
| Non-root Docker User | Runs as unprivileged user |
npm error EACCES: permission denied
Cause: The /home/nomad directory has wrong ownership.
Solution:
sudo chown -R 1001:1001 /home/nomadCause: WebSocket can't connect to backend.
Solution: Make sure you're mapping both ports:
-p FRONTEND_PORT:3000 -p BACKEND_PORT:4000Backend port must be FRONTEND_PORT + 1. Example: -p 3005:3000 -p 3006:4000
Cause: Secure cookies over HTTP.
Solution: Add -e COOKIE_SECURE=false to your docker run command.
Cause: Shell not found (old Docker image).
Solution: Pull the latest image:
docker pull guneyural0/nomad-ide:latestCause: Old Docker image.
Solution:
- Pull latest image:
docker pull guneyural0/nomad-ide:latest - Restart the container
Cause: Directory not mounted into container.
Solution: Add volume mount: -v /path/on/host:/path/in/container
Cause: Database file name mismatch.
Solution: The database is stored at /app/data/nomad-ide.db. Make sure your volume mount points to /app/data:
-v ~/nomad-ide-data:/app/data# 1. Stop and remove old container
docker stop nomad-ide && docker rm nomad-ide
# 2. Pull latest image
docker pull guneyural0/nomad-ide:latest
# 3. Run with same volume mounts (data persists)
docker run -d \
--name nomad-ide \
-p 3005:3000 \
-p 3006:4000 \
-v ~/nomad-ide-data:/app/data \
-v /home/nomad:/home/nomad \
-e JWT_SECRET="your-same-jwt-secret" \
-e COOKIE_SECURE=false \
--restart unless-stopped \
guneyural0/nomad-ide:latestKeep JWT_SECRET the same to preserve user sessions.
Contributions are welcome! Here's how to get started:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run
pnpm buildto ensure everything compiles - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License — see LICENSE for details.
Built for developers who code on the go.