๐ฅ๏ธ What Is a Server, Really?
This course assumes you've never touched a server before. Every term gets defined the moment it shows up โ nothing is assumed. Let's start at the very beginning.
A program is just a set of instructions a computer follows โ a calculator, a browser, a game. Normally that's simple: you double-click an icon, and it runs on your own computer.
But some programs need to be reachable by lots of people, from lots of devices, at any hour โ a shared photo album your whole family adds to, or a game you play against strangers. Nobody's personal laptop can handle that; it might be off, too slow, or on the other side of the planet.
A server is a computer whose job is to run a program continuously, stay turned on, and be reachable by other computers over a network. "Server" describes a role, not special hardware โ literally any computer can be one, including the exact machine you already own.
The computer asking for something is called a client. When you open a browser and visit a website, your browser is the client, and somewhere a server sends back the page.
Think of a server like a restaurant kitchen that never closes. Customers (clients) walk in anytime and place an order. The kitchen (server) is always staffed, always ready, and sends food back out the same way every time โ regardless of who's asking or when.
When your browser wants a webpage, it doesn't teleport there. It sends a message across a chain of connected devices โ your router, your provider's equipment, and internet infrastructure โ until it reaches the right server, which sends a response back.
Two rules ("protocols") matter most here:
- IP (Internet Protocol) โ every device gets a numeric address, an IP address, like
192.168.1.191, so others know where to send messages. - HTTP / HTTPS โ the rules browsers and servers use to talk. HTTPS is the encrypted version โ nearly every website uses it today.
Almost every modern app has a frontend (what the user sees and touches, running on their device) and a backend (the invisible server-side part that stores data, checks passwords, and coordinates between users). This entire course is about that backend half โ how to build, host, and secure it yourself.
๐งฉ Frontend, Backend, and the Operating System
Before touching any commands, let's get the mental model solid: what each "half" of an app does, and why servers usually run without a screen at all.
A simple example: a note-taking app. The frontend is the screen where you type. But if you want that note to still be there tomorrow, on a different device, something has to save it somewhere permanent and hand it back later โ that's the backend's job.
Frontend
Runs on the user's own device. Buttons, screens, images โ what people see and touch.
Backend
Runs on a server. Stores data, checks passwords, processes payments, coordinates users.
An operating system (OS) is the foundational software managing a computer's hardware โ Windows, macOS, Linux. Most people use a "desktop" version built around a mouse and windows. Servers usually run without a screen or mouse at all, managed entirely through typed commands, because:
- Nobody's sitting in front of it most of the time.
- A graphical interface eats memory and CPU that could run actual services instead.
- Text commands can be automated and repeated exactly โ critical for something running unattended for months.
This is why Ubuntu Server exists โ the same Linux under the hood as regular Ubuntu, but stripped of the graphical interface and tuned for always-on services. It's what this course uses throughout.
โจ๏ธ The Terminal & Remote Access (SSH)
From here on, almost everything happens by typing commands. Let's build real comfort with the terminal before anything else.
The terminal is a text interface where you type commands and the computer prints results back. A few things that trip up nearly every beginner:
- Commands are typed exactly โ one wrong space can silently do the wrong thing.
- No output usually means success. Terminals mostly speak up only when something's wrong.
- The
$shown in examples is a prompt, not something you type. - Spaces separate words โ the command, then its arguments.
- Case matters โ
File.txtโfile.txt, unlike Windows.
# Run a command as administrator (asks for your password) sudo apt update # Print a file's full contents cat filename # Search a file for matching lines, with line numbers grep -n "text to find" filename # Precisely replace text in a file, no editor needed sed -i 's|OLD|NEW|' filename
nano is the simplest terminal text editor. Open a file with nano filename:
Ctrl+OthenEnterโ saveCtrl+Xโ exitCtrl+Wโ search inside the file
cat filename after editing.
A heredoc, run directly at the shell prompt (not inside nano), writes exact multi-line content with zero risk of flattening:
cat > filename << 'EOF' line one line two line three EOF
You never want to sit at the server's physical keyboard for daily work. SSH (Secure Shell) opens a terminal session on the server from a completely different computer, fully encrypted:
ssh username@server-address
๐ IP Addresses, DNS & Domain Names
Now let's make the server findable โ first inside your own home, then to the entire internet.
Every device has an IP address, but there are two zones:
- Local (private) IP โ only meaningful inside your home network, handed out automatically by your router (DHCP). Looks like
192.168.1.x. - Public IP โ the one address your whole home shares with the internet. Your router sorts traffic to the right device inside (NAT โ more on this next lesson).
Your public IP is like your apartment building's street address. Your local IP is like your specific unit number. The whole internet only sees the street address โ your router (the doorman) figures out which unit the mail is actually for.
By default, your router might hand your server a different local IP every time it restarts โ a problem if you need to reliably reach it. The fix: either a DHCP reservation on the router, or a static configuration set on the server itself, via a system called netplan on Ubuntu Server.
network: ethernets: eno1: dhcp4: false addresses: - 192.168.1.191/24 routes: - to: default via: 192.168.1.1 nameservers: addresses: - 1.1.1.1 - 8.8.8.8 version: 2
Nobody wants to type 47.210.122.23 into a browser. A domain name stands in for an IP address. DNS (Domain Name System) translates names into addresses โ like a phonebook. Domains are bought through registrars (Namecheap, GoDaddy, Cloudflare).
The key record type is an A record, mapping a name (or subdomain, like api.example.com) straight to an IP address.
Most home connections don't have a fixed public IP โ it can change. If your DNS record points to an old IP, your domain silently breaks. Dynamic DNS (DDNS) fixes this: a small program (like ddclient) runs on your server, checks your current public IP, and automatically updates your DNS record the moment it changes.
๐ช Routers, Port Forwarding & Firewalls
Your domain now points at your home โ but two more gates stand between the internet and your server. Let's open them safely.
Your router uses NAT (Network Address Translation) to let every device in your home share one public IP address, tracking internally who's talking to what. Great for browsing โ but when an outsider tries to reach your public IP directly, the router has no idea which device that's for, and drops it by default.
A port is a numbered channel on top of an IP address โ the IP is a building's street address, a port is a specific suite number. HTTP conventionally uses port 80, HTTPS uses port 443.
Port forwarding is a router rule: "incoming traffic on port X goes to this specific device, on this specific internal port." Without it, outside visitors simply can't reach anything running on your home server.
A firewall controls what traffic is allowed in or out, based on rules โ only explicitly permitted traffic gets through. Once your server is reachable from the internet, it will be probed constantly by automated bots. A firewall dramatically shrinks what an attacker can even attempt.
sudo ufw allow 22/tcp # SSH sudo ufw allow 80/tcp # HTTP sudo ufw allow 443/tcp # HTTPS sudo ufw enable
๐ฆ Docker & Docker Compose
Time to actually run software reliably. This is the tool nearly every modern server relies on.
Installing software directly onto a server creates a classic headache: different programs often need conflicting versions of the same underlying tools. A server running several services can end up tangled and fragile.
Before standardized shipping containers, loading cargo was chaos โ sacks and barrels thrown into a ship's hold by hand. Then someone invented the identical steel box. Cranes and trucks stopped caring what was inside โ they just moved boxes. Docker does the exact same thing for software: it packages a program with everything it needs into one container that runs the same way anywhere.
- Image โ a reusable template for a container, a blueprint, usually downloaded ("pulled") from a public registry.
- Container โ a running instance of an image. Start, stop, and remove freely; the image stays intact.
- Dockerfile โ a text file with step-by-step instructions for building a custom image.
FROM node:20-alpine WORKDIR /app COPY package.json . RUN npm install COPY . . EXPOSE 3000 CMD ["node", "server.js"]
Real apps rarely need just one program. Docker Compose defines and runs several related containers together in one docker-compose.yml file.
services: hello-api: build: . restart: unless-stopped networks: - caddy_default networks: caddy_default: external: true
docker compose up -dโ build and start everything in the backgrounddocker compose logs -fโ watch live output, essential for troubleshootingdocker compose restartโ clean restart, often the right first fixdocker compose psโ see what's currently running
๐ Reverse Proxies & Automatic HTTPS
Running several services on one server means solving a real problem: only one program can own ports 80 and 443 at a time.
A reverse proxy is the one thing actually listening on ports 80/443. It inspects each incoming request โ usually by which domain name was requested โ and quietly forwards it to the right internal service, relaying the response back. Visitors never see or need to know about internal port numbers.
This is also the natural place to handle HTTPS certificates โ small files proving a server's identity and enabling encryption. Caddy is a modern reverse proxy that automates this entirely: point it at a domain, and it requests, installs, and renews a free certificate through Let's Encrypt, with zero manual steps.
api.example.com { reverse_proxy hello-api:3000 }
"For any request to api.example.com, forward it to whatever container is named hello-api, on port 3000" โ and Caddy silently handles the HTTPS certificate in the background. No manual renewal, ever.
๐ ๏ธ Building an API & Understanding Databases
Now let's actually build and deploy something โ and give it somewhere real to store data.
An API (Application Programming Interface) is a backend program that responds to requests โ typically with data, meant for other software to consume (a mobile app, a game, a frontend).
const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('Hello from the API!'); }); app.listen(3000, () => { console.log('API running on port 3000'); });
Wrap it in a Dockerfile, define it in a Compose file connected to the reverse proxy's network, add a matching Caddyfile block, and add a DNS A record for its subdomain. Once connected, it's reachable at a real domain, over real HTTPS, from anywhere. This exact pattern repeats for every new service you ever add.
Almost any real app needs to remember things permanently โ accounts, saved progress, messages. A database is specialized software for storing and quickly retrieving structured data, far more capable than plain text files.
PostgreSQL ("Postgres") is one of the most popular open-source databases, organizing data into tables โ like spreadsheets, with columns and rows. Apps talk to it using SQL (Structured Query Language).
๐ Authentication, Staying Safe & Your Final Project
The last stretch: real logins, a self-hosted backend platform, keeping it secure, and a project to make it all click.
Authentication verifies who someone is. Authorization decides what they're allowed to do once confirmed.
- Passwords are never stored in plain text โ they're run through hashing, a one-way scramble that's easy to verify but effectively impossible to reverse.
- Tokens (often a JWT โ JSON Web Token) prove you're already logged in, so the password doesn't need to be resent on every request.
Because getting this right from scratch is genuinely hard, most developers lean on a dedicated, well-tested system instead of rolling their own.
Supabase bundles the commonly-needed backend pieces โ a Postgres database, authentication, file storage, and a dashboard called Studio โ pre-integrated. It can be fully self-hosted using its official Docker Compose configuration, following the exact same deployment pattern as any other service on this stack: adjust its .env secrets, start it, connect it to the reverse proxy, add a Caddyfile block and DNS record.
The result: a real database, real user accounts, file storage, and an admin dashboard โ reachable at your own domain, secured with real HTTPS, entirely under your own control.
sudo apt update && sudo apt upgrade -y). Use a firewall. Use strong, unique, generated passwords and secrets โ never defaults. Avoid exposing raw admin access like SSH directly to the internet when tools like WireGuard or Tailscale exist. Never share a .env file.
A log is a running record of what a program has been doing โ the single most useful troubleshooting tool, because it shows what actually happened. docker compose logs -f is almost always the right first place to look.
Calm troubleshooting order: read the error fully โ check the relevant logs โ confirm config files actually contain what you meant (cat filename) โ change one thing at a time โ restart the affected piece and check again.
From an Ordinary Computer to a Real, Reachable Server
Bring every lesson together into one real, working stack:
- Install Ubuntu Server on any spare machine and confirm you can SSH into it from another computer.
- Set a static local IP, and register (or reuse) a domain with Dynamic DNS pointed at your home.
- Forward ports 80 and 443 on your router, and enable UFW โ remembering to allow SSH first.
- Install Docker and Docker Compose.
- Deploy Caddy as a reverse proxy, and confirm it gets a real HTTPS certificate for your domain.
- Build a tiny Node/Express "hello world" API, containerize it, and get it live at a subdomain over HTTPS.
- Self-host Supabase, wire it into the same reverse proxy, and log into its dashboard at your own domain.
ssh user@ip โ connect to your serversudo ufw allow 22/tcp && sudo ufw enable โ firewall, SSH-safedocker compose up -d โ start a stackdocker compose logs -f โ watch what's happeningdocker network connect NETWORK CONTAINER โ link two stacks togethersudo apt update && sudo apt upgrade -y โ keep the OS patched