Featured image of post Self-Hosted VPS Remote Development Environment: Code-Server + Jupyter + Git Full Setup

Self-Hosted VPS Remote Development Environment: Code-Server + Jupyter + Git Full Setup

Say goodbye to local environment setup headaches. Deploy a unified VS Code + Jupyter + Git remote development platform on your VPS, code and run experiments from any device, anywhere, with zero local hardware dependency

Introduction

Do you experience any of these scenarios?

  • Switched to a new computer and spent half a day just setting up the dev environment
  • Home computer is too weak to run AI models or compile large projects
  • Want to code on a tablet/iPad, but realize it’s impossible
  • Collaborating with teammates, but their environment differs from yours: “It works on my machine”
  • Midnight emergency fix needed, but your home computer is shut down

All these problems share one solution: move your development environment to a VPS.

This article walks you through building a complete VPS remote development environment: VS Code in the browser (Code-Server) + JupyterLab + Git, behind Nginx reverse proxy with HTTPS encryption and authentication. Code, run experiments, and manage your projects from any device, any browser, seamlessly.


1. Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                    Your Any Device                              │
│   Mac / Windows / Linux / iPad / Phone  →  Browser             │
└────────────────────────────┬────────────────────────────────────┘
                             │ HTTPS
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                    VPS (Ubuntu 24.04)                           │
│                                                                 │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────────┐   │
│  │   Nginx      │──▶│  Code-Server │   │  JupyterLab      │   │
│  │  :443 HTTPS  │   │  :8080       │   │  :8888           │   │
│  │  Reverse     │   │  VS Code     │   │  Notebook Env    │   │
│  │  Proxy       │   │  in Browser  │   │                  │   │
│  └──────┬───────┘   └──────┬───────┘   └────────┬─────────┘   │
│         │                  │                     │             │
│         │                  └────────┬────────────┘             │
│         │                           ▼                          │
│         │                  ┌─────────────────┐                 │
│         │                  │   Docker        │                 │
│         │                  │  Git Repos      │                 │
│         │                  │  Python/Node    │                 │
│         │                  │  Toolchains     │                 │
│         │                  └─────────────────┘                 │
│                                                                 │
│  Domain: dev.yourdomain.com → Nginx Routes                      │
└─────────────────────────────────────────────────────────────────┘

2. Prerequisites

2.1 Minimum VPS Specs

Use CaseMinimumRecommended
Code-Server basic dev2 CPU / 4GB RAM / 40GB Disk4 CPU / 8GB RAM / 100GB SSD
With Jupyter + AI projects4 CPU / 8GB RAM / 80GB Disk8 CPU / 16GB RAM / 200GB NVMe
Multi-container + GPU8 CPU / 32GB RAM / 500GB SSD16 CPU / 64GB RAM + GPU

2.2 System Initialization

# Update the system
sudo apt update && sudo apt upgrade -y

# Install base tools
sudo apt install -y curl wget git unzip htop net-tools

# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

# Install Nginx
sudo apt install -y nginx certbot python3-certbot-nginx

2.3 Domain DNS

Point dev.yourdomain.com to your VPS IP:

# DNS A record
dev.yourdomain.com  →  Your VPS IP

3. Deploy Code-Server (VS Code in Browser)

3.1 Docker Deployment

# Create working directories
mkdir -p ~/dev-environment/code-server/{data,projects}
cd ~/dev-environment

# Pull and start Code-Server
docker run -d \
  --name code-server \
  --restart unless-stopped \
  -p 127.0.0.1:8080:8080 \
  -v ~/dev-environment/projects:/home/coder/projects \
  -v ~/dev-environment/code-server/data:/home/coder/.local/share/code-server \
  -e PASSWORD=YourStrongPassword \
  -e TZ=Asia/Shanghai \
  -e PROXY_DOMAIN=dev.yourdomain.com \
  ghcr.io/coder/code-server:latest

3.2 Verify Deployment

# Check container status
docker ps | grep code-server

# Test locally via SSH tunnel
ssh -L 8080:localhost:8080 your-vps
# Then open http://localhost:8080 in your browser

After logging into Code-Server, install these from the marketplace:

ExtensionPurpose
Chinese Language PackChinese UI
Remote - SSHSSH remote development
PythonPython language support
GoGo language support
PrettierCode formatting
ESLintJavaScript/TypeScript linting
GitLensGit enhancement
Project ManagerMulti-project management
Material Icon ThemeFile icon美化
Error LensIn-line error hints

4. Deploy JupyterLab

4.1 One-Command Docker Deployment

mkdir -p ~/dev-environment/jupyter/{notebooks,projects}
cd ~/dev-environment

docker run -d \
  --name jupyter \
  --restart unless-stopped \
  -p 127.0.0.1:8888:8888 \
  -v ~/dev-environment/projects:/home/jovyan/work \
  -v ~/dev-environment/jupyter/notebooks:/home/jovyan/notebooks \
  -e PASSWORD=YourJupyterPassword \
  -e TZ=Asia/Shanghai \
  jupyter/base-notebook:latest

4.2 Choose the Right Image

# Basic (minimal)
jupyter/base-notebook

# Python + Scientific computing
jupyter/scipy-notebook

# AI/ML complete stack
jupyter/pyspark-notebook

# GPU support (requires nvidia-container-toolkit)
jupyter/torch-notebook

4.3 Install Extra Python Packages

# Enter the container
docker exec -it jupyter bash

# Install common packages
pip install numpy pandas matplotlib seaborn scikit-learn
pip install torch torchvision torchaudio
pip install transformers datasets accelerate
pip install langchain langgraph llama-index

# Save as custom image
docker commit jupyter jupyter:custom

5. Nginx Reverse Proxy Configuration

5.1 Get SSL Certificate

sudo certbot --nginx -d dev.yourdomain.com --non-interactive --agree-tos -m your@email.com

5.2 Nginx Configuration

sudo tee /etc/nginx/sites-available/dev.yourdomain.com << 'EOF'
server {
    listen 443 ssl http2;
    server_name dev.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/dev.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/dev.yourdomain.com/privkey.pem;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000" always;

    # Code-Server
    location / {
        proxy_pass                         http://127.0.0.1:8080;
        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_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass                 $http_upgrade;
        proxy_read_timeout                  86400;
    }

    # JupyterLab
    location /jupyter/ {
        proxy_pass                         http://127.0.0.1:8888/jupyter/;
        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_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout                  86400;
        proxy_set_header X-Frame-Options   "";
    }

    # Static assets
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        proxy_pass http://127.0.0.1:8080;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

# HTTP → HTTPS redirect
server {
    listen 80;
    server_name dev.yourdomain.com;
    return 301 https://$host$request_uri;
}
EOF

sudo ln -sf /etc/nginx/sites-available/dev.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

5.3 Auto-Renew Certificates

# Test renewal
sudo certbot renew --dry-run

# Verify timer is active
sudo systemctl status certbot.timer

6. Git Integration & Code Management

6.1 Configure Git

# Inside Code-Server terminal, set global config
git config --global user.name "Your Name"
git config --global user.email "you@email.com"
git config --global core.editor "code --wait"
git config --global init.defaultBranch main

# Generate SSH key
ssh-keygen -t ed25519 -C "your@email.com"
cat ~/.ssh/id_ed25519.pub  # Add to GitHub/GitLab

6.2 Project Directory Structure

~/projects/
├── web-app/              # Web project
│   ├── src/
│   ├── package.json
│   └── README.md
├── ml-experiments/       # AI/ML project
│   ├── notebooks/
│   ├── models/
│   └── requirements.txt
├── scripts/              # Utility scripts
└── personal/             # Personal notes
    └── obsidian-vault/

6.3 Code Sync Strategies

# Option 1: Git push to remote (recommended)
cd ~/projects/my-project
git init
git remote add origin git@github.com:username/my-project.git
git add .
git commit -m "Initial commit"
git push -u origin main

# Option 2: rsync sync to local machine
rsync -avz --progress coder@your-vps:~/projects/my-project/ ./my-project/

# Option 3: syncthing for bidirectional sync
# See: https://syncthing.net/

7. Security Hardening

7.1 Firewall Configuration

# Only open necessary ports
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp      # SSH
sudo ufw allow 80/tcp      # HTTP (for redirect)
sudo ufw allow 443/tcp     # HTTPS
sudo ufw enable

7.2 Fail2ban Protection

sudo apt install -y fail2ban

sudo tee /etc/fail2ban/jail.local << 'EOF'
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600

[code-server]
enabled = true
port = http,https
filter = http-auth
logpath = /home/coder/.local/share/code-server/logs
maxretry = 5
bantime = 3600
EOF

sudo systemctl enable fail2ban
sudo systemctl restart fail2ban

7.3 Backup Configuration

# Regular backup of dev environment
sudo tar czf /backup/dev-environment-$(date +%Y%m%d).tar.gz \
  ~/dev-environment/ \
  ~/.ssh/ \
  ~/.gitconfig

# Upload to object storage (optional)
aws s3 cp /backup/dev-environment-$(date +%Y%m%d).tar.gz \
  s3://your-backup-bucket/dev-environment/

8. Advanced: Docker Compose One-Command Start

Combine all services into one docker-compose.yml:

# ~/dev-environment/docker-compose.yml
version: "3.8"

services:
  code-server:
    image: ghcr.io/coder/code-server:latest
    container_name: code-server
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    volumes:
      - ./projects:/home/coder/projects
      - ./code-server/data:/home/coder/.local/share/code-server
    environment:
      - PASSWORD=${CS_PASSWORD}
      - TZ=Asia/Shanghai
      - PROXY_DOMAIN=dev.yourdomain.com
    networks:
      - dev-net

  jupyter:
    image: jupyter/scipy-notebook:latest
    container_name: jupyter
    restart: unless-stopped
    ports:
      - "127.0.0.1:8888:8888"
    volumes:
      - ./projects:/home/jovyan/work
      - ./jupyter/notebooks:/home/jovyan/notebooks
    environment:
      - PASSWORD=${JPY_PASSWORD}
      - TZ=Asia/Shanghai
    networks:
      - dev-net

networks:
  dev-net:
    driver: bridge
# Create .env file
cat > ~/dev-environment/.env << EOF
CS_PASSWORD=YourCodeServerPassword
JPY_PASSWORD=YourJupyterPassword
EOF

# Start everything
cd ~/dev-environment
docker compose up -d

# View logs
docker compose logs -f

9. Real-World Use Cases

9.1 Code on iPad

iPad Safari → dev.yourdomain.com → Code-Server
                ↓
         Full VS Code experience
         Keyboard + mouse (or touch)
         Code auto-saved to Git

9.2 Run AI Experiments Without Using Home Power

# Train on VPS, monitor from home computer
cd ~/projects/ml-experiments
python train.py --epochs 100  # Running on your VPS
# Monitor training curves live via Jupyter

9.3 Instant Team Collaboration

# Log in from any device → identical environment
git clone https://github.com/team/project.git
code project/  # Open directly in browser
# Code auto-saves, never lose your work

10. FAQ

Q1: Code-Server feels slow, what to do?

# Check bandwidth usage
iftop -i eth0

# Disable animations in Code-Server
# Settings → Appearance → Reduce Animation

Q2: How to share the dev environment with team members?

# Option 1: Each person gets their own container, shared projects directory
# Option 2: Use DevContainer standard (GitHub Codespaces-style)
# Option 3: Add Nginx basic auth to restrict access

Q3: Running out of disk space?

# Clean up unused Docker resources
docker system df          # Check usage
docker system prune -a    # Clean up (use with caution)
docker volume prune       # Clean anonymous volumes

# Move projects to larger disk
mv /home/coder/projects /mnt/large-disk/projects
ln -s /mnt/large-disk/projects /home/coder/projects

Q4: Forgot the password?

# Code-Server password is in the config
cat ~/dev-environment/code-server/data/User/settings.json
# Or reset directly
docker exec code-server passwd coder

Summary

Through this article, you’ve learned:

CapabilityTool
VS Code in BrowserCode-Server
Interactive ProgrammingJupyterLab
Code Version ControlGit + GitHub
Secure AccessNginx + HTTPS + Fail2ban
One-Click DeployDocker Compose

Key Benefits:

  • 🎯 Consistent Environment: Same setup on any device
  • 💰 Cost Savings: Cheap VPS replaces high-end local machine
  • 🔒 Data Security: Code stays on your server
  • 🚀 Productivity Boost: Start coding anytime, unbound by hardware
  • 🌍 Mobile Office: Code from phone/iPad too

Visit https://dev.yourdomain.com and start your cloud development journey!

📺 看视频版教程 → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials