After this lesson, you will be able to: Set up a fresh Ubuntu VPS, secure it (SSH keys, no root, UFW firewall), deploy a real web app to it manually, then automate the deploy with a bash script.
This is the passion project for the Linux sub-track per Curriculum-Upgrade.md. Spend ~$5 (DigitalOcean / Hetzner / Vultr / Linode all work) and a Saturday afternoon. The result: a public URL with your name on it that proves you can run a Linux server.
Cheapest provider that fits is fine. DigitalOcean is the canonical learning choice.
Sign up at digitalocean.com (or hetzner.com, vultr.com, linode.com)
Create a $5/mo droplet: Ubuntu 22.04 LTS, smallest size, region near you
When prompted for authentication: paste your SSH public key (cat ~/.ssh/id_ed25519.pub). DO NOT pick password.
Note the public IP
From your laptop: ssh root@<ip>
If that works, you have a server. If not, double-check the SSH key.
Every box that touches the internet should pass this list.
Create a non-root user: adduser deploy ; usermod -aG sudo deploy
Copy your SSH key to the deploy user: mkdir /home/deploy/.ssh ; cp /root/.ssh/authorized_keys /home/deploy/.ssh/ ; chown -R deploy:deploy /home/deploy/.ssh ; chmod 700 /home/deploy/.ssh ; chmod 600 /home/deploy/.ssh/authorized_keys
From your laptop: ssh deploy@<ip>, must work before continuing
Edit /etc/ssh/sshd_config: PermitRootLogin no, PasswordAuthentication no, save, sudo systemctl restart sshd
VERIFY in a parallel terminal that deploy@<ip> still works BEFORE closing your other session
Install UFW firewall: sudo apt install ufw ; sudo ufw allow 22 ; sudo ufw allow 80 ; sudo ufw allow 443 ; sudo ufw enable
Install fail2ban: sudo apt install fail2ban (default config bans IPs after 5 SSH failures)
Enable unattended-upgrades: sudo apt install unattended-upgrades ; sudo dpkg-reconfigure -plow unattended-upgrades
Pick any app you've built. Or use a 'Hello World' Express / Flask app to start.
On the VPS as deploy: sudo apt install nginx nodejs npm git
Clone your repo: git clone https://github.com/<you>/<repo>.git /srv/myapp
cd /srv/myapp ; npm ci ; npm run build (if applicable)
Create a systemd unit (see do-linux-04 lesson) at /etc/systemd/system/myapp.service
sudo systemctl daemon-reload ; sudo systemctl enable --now myapp
Configure nginx as a reverse proxy: copy a sample config to /etc/nginx/sites-available/myapp; symlink to sites-enabled; reload nginx
Point a domain (or use the IP directly) at the box
Use certbot to install a free Let's Encrypt cert: sudo apt install certbot python3-certbot-nginx ; sudo certbot --nginx -d yourdomain.com
Replace manual deploy with a one-command script. Put it in /usr/local/bin/deploy.sh.
#!/usr/bin/env bashset -euo pipefailIFS=$'\n\t'APP_DIR=/srv/myappLOG=/var/log/myapp/deploy.loglog() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG"; }log 'Starting deploy'cd "$APP_DIR"log 'Pulling latest'git fetch --all --quietgit reset --hard origin/mainlog 'Installing deps'npm ci --omit=devlog 'Building'npm run buildlog 'Restarting service'sudo systemctl restart myapplog 'Verifying'sleep 3if curl -sf http://localhost/health > /dev/null; thenlog 'Deploy OK'elselog 'HEALTH CHECK FAILED, rolling back'git reset --hard HEAD~1sudo systemctl restart myappexit 1fi
Skipping the deploy script, manual deploys don't impress. Forgetting HTTPS. Free Let's Encrypt is 10 lines; no excuse. Storing secrets in the deploy script. Put them in /etc/myapp/env (root:deploy 640). Leaving the IP exposed without UFW + fail2ban. Skipping the health check + rollback. The script that catches its own failure is the script that proves you've done this before.
Sign in and purchase access to unlock this lesson.