Code from your phone. Junior Swarm gives you isolated Docker containers with a full terminal, git integration, and file uploads — accessible from any device via a mobile-first web app.
- You deploy the server on any machine with Docker
- Open the web app from your phone or browser
- Create a project (optionally clone a GitHub repo, including private ones)
- Get a full terminal with colors, cursor support, and resize
- Upload files from your device directly into the container
- Docker and Docker Compose (for the server)
- Flutter 3.11+ (only if you want to build the mobile app yourself)
git clone https://github.com/programmism/junior-swarm.git
cd junior-swarm
cp .env.example .envEdit .env and set a strong API_KEY:
API_KEY=your-secret-key-here
PORT=8080
Start the server:
docker compose up -dVerify it's running:
curl http://localhost:8080/healthz
# {"status":"ok"}The Flutter web app is in the app/ directory. To build and serve it:
cd app
flutter pub get
flutter run -d chromeOr build for deployment:
flutter build web
# Serve app/build/web with any static file server (nginx, caddy, etc.)- Open the app in your browser
- Enter your server URL (e.g.
http://your-server:8080) - Enter your API key
- Tap Connect
If you already have nginx on the host (e.g. serving multiple services), add Junior Swarm as another site.
cd app
flutter pub get
flutter build web
sudo mkdir -p /var/www/junior-swarm
sudo cp -r build/web/* /var/www/junior-swarm/Create /etc/nginx/sites-available/junior-swarm:
server {
listen 80;
server_name YOUR_DOMAIN;
root /var/www/junior-swarm;
index index.html;
# API requests -> control plane
location /api/ {
proxy_pass http://127.0.0.1:8080/;
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;
}
# WebSocket terminal sessions
location /sessions/ {
proxy_pass http://127.0.0.1:8080/sessions/;
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;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
# Flutter web SPA
location / {
try_files $uri $uri/ /index.html;
}
}Enable and reload:
sudo ln -s /etc/nginx/sites-available/junior-swarm /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxRequires a domain pointed at the server — Let's Encrypt cannot issue certs for bare IPs.
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d YOUR_DOMAINCertbot will obtain the certificate and update the nginx config to enable SSL and redirect HTTP to HTTPS.
Verify auto-renewal:
sudo certbot renew --dry-runTap + and fill in:
- Project Name — any name you want
- GitHub Repository URL — optional, will be cloned on start
- GitHub Token — for private repos, paste a Personal Access Token with
reposcope - Base Image — defaults to
ubuntu:22.04
- Tap your project, then tap Start
- If you provided a GitHub URL, the repo is auto-cloned into
/workspace - Tap Terminal to open a full xterm session (colors, vim, htop — everything works)
In the terminal screen, tap the attach icon in the top bar to pick and upload files from your device. Files are uploaded to /uploads inside the container.
| Variable | Default | Description |
|---|---|---|
API_KEY |
(empty = auth disabled) | Bearer token for all API requests |
PORT |
8080 |
Host port to expose |
All endpoints except /healthz require Authorization: Bearer <API_KEY> header.
GET /healthz
Create project:
curl -X POST http://localhost:8080/projects \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-app",
"github_url": "https://github.com/user/repo",
"github_token": "ghp_...",
"base_image": "ubuntu:22.04"
}'The github_url, github_token, and base_image fields are optional.
List projects:
GET /projects
Get / Start / Stop / Delete project:
GET /projects/{id}
POST /projects/{id}/start
POST /projects/{id}/stop
DELETE /projects/{id}
Create session (project must be running):
curl -X POST http://localhost:8080/projects/{id}/sessions \
-H "Authorization: Bearer $API_KEY"Connect via WebSocket:
ws://localhost:8080/sessions/{id}/terminal?token=<API_KEY>
Messages are JSON:
- Client sends:
{"type": "input", "data": "ls\n"}or{"type": "resize", "cols": 120, "rows": 40} - Server sends:
{"type": "output", "data": "..."}
curl -X POST http://localhost:8080/sessions/{id}/upload \
-H "Authorization: Bearer $API_KEY" \
-F file=@myfile.txtdocker compose up
+-- socket-proxy (Docker socket proxy, least-privilege)
+-- control-plane (Go API server)
+-- REST API + Bearer auth middleware
+-- WebSocket terminal (docker exec + tmux)
+-- File upload (multipart -> container)
+-- SQLite (projects + sessions)
cd server
go test ./...
go run ./cmd/devbox