A cloud-based development environment gives you the freedom to code from anywhere while maintaining a consistent setup that's always ready to go. Let's walk through the process of setting up a Linux-based cloud virtual machine (VM) for remote development.
Here are the steps we'll follow:
As you execute the commands shown below, take a moment to understand exactly what each command does, and use an AI tool like ChatGPT or Claude to troubleshoot issues.
Nerd Fonts are modified versions of popular programming fonts that include icons useful for displaying file types, git status, and other symbols in terminal-based tools like Neovim and Zellij. I use Meslo Nerd Font with the Ghostty terminal on macOS. To set up a nerd font:
Download and install a nerd font for your operating system: Linux, macOS, or Windows. Example: brew install font-meslo-lg-nerd-font
installs Meslo Nerd Font on macOS.
Configure your terminal to use the installed font: Linux Terminal, Windows Terminal, iTerm2 (macOS), Alacritty, Ghostty. Example: On macOS, head to "Ghostty" > "Settings" and add the line font-family = MesloLGS Nerd Font Mono
to use Meslo Nerd Font.
Try a few different nerd fonts and experiment with terminal settings like font size, theme, background color, opacity, etc. to arrive at a setup that looks good and works well for you.
We'll use a command-line tool called SSH (Secure Shell) to access and operate a cloud VM remotely from a local machine (e.g., your laptop). While we can log in to a cloud VM via SSH with just a username and password, using an SSH key pair is a much more secure method.
Let's generate a new SSH key pair on our local machine. Open up a new terminal on Linux, macOS, or Windows WSL and run the following command:
ssh-keygen -t ed25519 -f ~/.ssh/devbox -C "devbox"
TIP: You can replace the name devbox
above and hereafter with any name of your choice.
You'll be prompted to enter an optional passphrase to encrypt the private key, adding yet another layer of security. If you set one, you'll be asked to enter it every time you use the key. Let's verify that the key pair was created in the directory ~/.ssh
using the ls
command:
ls ~/.ssh
You should see two key files: a private key devbox
and a public key devbox.pub
.
Rent a cloud VM with at least 2 GB RAM on a platform like Hetzner Cloud or DigitalOcean. The cheapest VM on Hetzner ($5 per month for 2 CPU cores, 4 GB RAM, and 40 GB disk space) can comfortably support the development workflow for a typical web application.
While configuring the VM, make sure to enable the assignment of a public IPv4 address and select "SSH Key" as the authentication method (instead of a "root" user password). To add your public SSH key to the VM, first run the following command on your local machine:
cat ~/.ssh/devbox.pub
Then, copy the result displayed and paste it into the "Add SSH Key" dialog on Hetzner[3]:
Select the Ubuntu OS, pick a name (e.g. devbox
), create the VM, and note its IPv4 address:
TIP: Connect a domain name to your VM to avoid having to remember the IP address.
We've successfully created a cloud VM, added an SSH public key to it, and configured a domain to point to its IP address. We can now log in to the VM via SSH using the private key created earlier. Just run the following command in a terminal on your local machine:
ssh -i ~/.ssh/devbox [email protected]
You'll be presented with the warning: Are you sure you want to continue connecting?
. Type yes
and press Enter/Return to continue. You're now remotely logged in to the cloud VM as the root
user (the $
prompt changes to #
). Run the following command:
hostnamectl
The command's output shows the machine name (e.g. devbox
) and other hardware details.
APT (Advanced Package Tool) is Ubuntu's package management system for installing, updating, and removing software easily. First, let's fetch the latest index of packages:
sudo apt update
Next, let's install some commonly used command line tools and utilities for development:
sudo apt install \
git gh htop zsh fzf nano \
man-db zip unzip ufw wget \
jq dnsutils ripgrep fd-find gcc
Here's what each of the above tools does:
git
- Git CLI for downloading, modifying, and updating source code repositoriesgh
- GitHub's official CLI for authenticating and interacting with GitHub projectszsh
- Replacement for bash
shell with better tab completion and customizationfzf
- Fuzzy finder for quickly searching and filtering files, commands, and textnano
- A simple, user-friendly command line text editor for quick file modificationsman-db
- Manual page database and viewer for accessing system documentationzip
& unzip
- Utilities for compressing files or extracting them from ZIP archivesufw
(Uncomplicated firewall) - simplified interface for managing network firewall ruleswget
- Tool for downloading files and web pages from URLs via HTTP/HTTPS/FTPjq
- Lightweight command-line processor for parsing and manipulating JSON datahtop
- Interactive process viewer & system monitor with a colorful, user-friendly UIdnsutils
- DNS lookup utilities including dig
, nslookup
, and host
commandsripgrep
- Ultra-fast text search tool to recursively search directories using regexfd-find
- Simple, fast, and user-friendly alternative to the Linux find
commandgcc
- GNU Compiler Collection for compiling C, C++, and several other languagesYou can learn more about any of these tools using the man
command (e.g. man jq
). Run the sudo apt upgrade
command from time to upgrade packages to their latest versions.
Zsh (Z shell) is a replacement for the system-default Bash shell with support for better tab completion and customization via plugins. Let's create a Zsh configuration file using nano
:
TERM=xterm-256color nano $HOME/.zshrc
Paste the following text into the file, then press Ctrl+O Enter
to save, and Ctrl+X
to exit:
# Enable full color support
export TERM=xterm-256color
# Install (if absent) and initialize zinit plugin manager
ZINIT_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}/zinit/zinit.git"
[ ! -d $ZINIT_HOME ] && mkdir -p "$(dirname $ZINIT_HOME)"
[ ! -d $ZINIT_HOME/.git ] && git clone https://github.com/zdharma-continuum/zinit.git "$ZINIT_HOME"
source "${ZINIT_HOME}/zinit.zsh"
# User the Powerlevel10k theme
zinit ice depth=1; zinit light romkatv/powerlevel10k
# Essential Zsh plugins
zinit light zsh-users/zsh-syntax-highlighting
zinit light zsh-users/zsh-completions
zinit light zsh-users/zsh-autosuggestions
# git shortcuts: https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git
zinit snippet OMZP::git
zinit snippet OMZP::gh
# Register completions from plugins
autoload -U compinit && compinit
zinit cdreplay -q
# Load Powerlevel10k configuration; To customize run `p10k configure`
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
# Save command history
HISTSIZE=5000
HISTFILE=~/.zsh_history
SAVEHIST=$HISTSIZE
HISTDUP=erase
setopt appendhistory
setopt sharehistory
setopt hist_ignore_space
setopt hist_ignore_all_dups
setopt hist_save_no_dups
setopt hist_ignore_dups
setopt hist_find_no_dups
# Enable case insensitive completions
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}'
# Show colors in ls output
alias ls='ls --color'
# Use fzf for searching command history
source /usr/share/doc/fzf/examples/completion.zsh
source /usr/share/doc/fzf/examples/key-bindings.zsh
The above configuration offers a minimal yet powerful initial setup for software development. You can continue customizing your setup over time with more plugins and productivity tools.
Finally, let's make the switch from Bash to Zsh using the chsh
command:
chsh -s $(which zsh)
Quit the current session by executing the exit
command and start a new session using the SSH command used earlier. Upon logging back in, you will be prompted to configure the Powershell10k Zsh theme. Respond as follows for a minimal but comprehensive prompt:
Make sure to select "(3) Off" for "Instant Prompt Mode" to avoid layout issues with Zellij. Use the command p10k configure
to relaunch the wizard and customize your prompt.
Zellij is a terminal workspace that allows you to create and manage long-running multi-pane terminal sessions within a single window. Run these commands to download and install Zellij:
wget https://github.com/zellij-org/zellij/releases/download/v0.42.2/zellij-x86_64-unknown-linux-musl.tar.gz
tar -xvf zellij*.tar.gz
chmod +x zellij
sudo mv zellij /usr/local/bin
Replace v0.42.2
in the URL above with the latest version of Zellij from the releases page, and replace x86_64
with arm64
if your Linux VM is running on an ARM-based processor. To launch Zellij automatically on logging in, let's update our Zsh configuration using nano
:
nano $HOME/.zshrc
Add these lines at the end of the file, then press Ctrl+O Enter
to save, and Ctrl+X
to exit:
# Attach to existing Zellij session, or start one if required
ZELLIJ_AUTO_ATTACH=true
eval "$(zellij setup --generate-auto-start zsh)"
alias zexit='if [ -n "$ZELLIJ" ]; then zellij action detach && exit; else exit; fi'
Quit the current session by executing the exit command and start a new session using the SSH command used earlier. Upon logging back in, you will be prompted to configure Zellij. Select "Unlock First (non-colliding)" to avoid keybinding conflicts with other CLI tools:
Now you can create multiple tabs and panes using Zellij's on-screen keyboard shortcuts. Your Zellij session will continue running in the background even if you close your terminal window. When you SSH into the VM again, Zellij will reattach to the existing session.
Neovim is a modern, extensible terminal-based code editor. We'll install Neovim and set up LazyVim, a preconfigured collection of plugins that turn Neovim into a full-fledged integrated development environment (IDE). Run these commands to install Neovim and set up LazyVim:
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz
sudo rm -rf /opt/nvim
sudo tar -C /opt -xzf nvim-linux-x86_64.tar.gz
rm nvim-linux-x86_64.tar.gz
sudo ln -s /opt/nvim-linux-x86_64/bin/nvim /usr/local/bin/nvim
git clone https://github.com/LazyVim/starter $HOME/.config/nvim
Replace x86_64
with arm64
above if your Linux VM is running on an ARM-based processor.
Quit the current session by executing the exit
command and start a new session using the SSH command used earlier. Upon logging back in, run the nvim
command to start Neovim and initialize all the plugins included with LazyVim. Watch this short tutorial to learn more.
NOTE: You can also configure Visual Studio Code to develop on the VM remotely over SSH.
Run the following authenticate the GitHub gh
CLI to clone, develop, and push source code:
gh auth login
Select the following responses for the questions displayed on the screen:
Open the URL https://github.com/login/device in a web browser on your local machine and enter the 6-character code (e.g. C362-0740
) shown on the terminal to complete the setup.
Create a work
directory, clone a GitHub repository, and open it up in Neovim:
mkdir -p $HOME/work && cd $HOME/work
git clone https://github.com/aakashns/nextjs-starter.git
cd nextjs-starter
nvim .
Create a second Zellij tab by pressing Ctrl+g
, then t
, then n
. You can use this tab to install dependencies, run the application, check logs, etc. Let's install Node.js with nvm
:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
\. "$HOME/.nvm/nvm.sh"
nvm install 24
Next, enter the application folder, install dependencies, and run the development server:
cd ~/work/nextjs-starter
npm install
npm run dev
You can now open up the URL http://your-vm's-ip:3000 (e.g. http://5.78.129.4:3000 ) in a browser to interact with the web app. It reloads automatically as you edit & save code files:
Thanks to Zellij, the application continues running even if you close the terminal window. You can pick up right where you left off when you SSH back into the VM. If you have write access to the repository, you can commit & push your changes back to GitHub using the git
CLI.
Note that our web app is currently publicly accessible over the internet. We'll use ufw
(Uncomplicated Firewall) to prevent unauthorized access to the VM. Execute the following commands in the VM to block all incoming network traffic except our SSH connection:
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw enable
Now, close the terminal window and log back into the VM with the following SSH command:
ssh -i ~/.ssh/devbox \
-L 3000:localhost:3000 \
-o ServerAliveInterval=60 \
-o ServerAliveCountMax=3 \
[email protected]
Replace 5.78.129.4
with your VM's public IP and change 3000
to the port your app uses.
This above command sets up port forwarding to access port 3000 on the remote server through localhost:3000, and configures keep-alive settings to maintain the connection. The web app is now accessible at http://localhost:3000 (but not at http://your-vm's-ip:3000).
We've set up a powerful, secure, and easy-to-use remote development environment with many modern tools and utilities. You can launch your workspace instantly with a single SSH command and develop from any device while maintaining a consistent development setup.