PES
EN English 한국어 日本語 中文 हिन्दी বাংলা اردو Español Português Français Deutsch Русский العربية Bahasa Indonesia Tiếng Việt ไทย Türkçe

How to enable notifications

Notifications are unavailable in this browser or on this address. Try a supported browser on the secure PES site.

If notifications are blocked, allow them in your browser settings.

  1. Open the site controls beside the address bar, or your browser’s site settings. Set Notifications for this site to Allow.
  2. In your device settings, allow notifications for your browser and check Do Not Disturb or Focus.
  3. Return to PES, reload the page, and use the bell to enable notifications.

On iPhone or iPad, add PES to your Home Screen, open it there, then allow notifications.

My learning progress
  1. How to use AI
  2. Terminal commands
  3. Advanced AI
  4. Vibe coding
  5. AI certification Getting ready
  6. Keyboard
See details
All Vibe Coding topics
VIBE CODING

Web Server

Twenty lines of shell that a browser will talk to.

A web server is simply a program that waits for a request and sends a response. To prove it, we build one out of bash, netcat and a named pipe — you can read every line of it — and then move to Caddy, the server that runs real sites with HTTPS included.

Docker: Caddy in a box, then real sites on the internet

packs a program together with everything it needs to run — libraries, settings, even a small operating system — into a single , and runs that image as a . A container behaves the same on your laptop and on a rented server, so the old excuse "but it works on my machine" disappears.

Caddy is already a single file, so why bother? Because a real soon runs more than one thing — a site, an app, a . Docker gives each one its own box that you start, stop, update and delete with the same few commands, without installing anything else on the server or disturbing its neighbours.

Four words to know first

  1. ImageA frozen package of a program and everything it needs. You download one (caddy:2) or build your own from a recipe file called a Dockerfile. It never changes while it runs.
  2. ContainerAn image that is running. One image can start many containers, and deleting a container never touches the image — the next start is fresh.
  3. Port mappingA container has its own ports that nobody outside can see. -p 8080:80 connects port 8080 of your computer to port 80 inside the container, so a browser can reach it.
  4. VolumeThe files a container writes inside itself vanish when it is deleted. A volume, or one of your folders mounted into it, is storage that stays — your pages, your settings and Caddy's certificates live there.

Installing Docker

On a Mac or a Windows PC, install Docker Desktop from docker.com and start it once; after that the docker command works in the terminal. On a Linux server, Docker's official script installs everything in one line. Both check commands should answer before you go on.

install
# macOS · Windows: install Docker Desktop from docker.com and start it once

# Ubuntu / Debian server: Docker's official install script
curl -fsSL https://get.docker.com | sh

# check that both commands answer
docker --version
docker compose version

Caddy in one command

Go to a folder that holds an index.html and run the line below. The first time, Docker downloads the Caddy image; after that it starts in about a second. Open http://localhost:8080 and your page is there.

terminal
# serve this folder at http://localhost:8080 (Control-C stops it)
docker run --rm -p 8080:80 -v "$PWD":/usr/share/caddy caddy:2
  1. docker runCreate a container from an image and start it.
  2. --rmDelete the container as soon as it stops, so stopped containers do not pile up. The image stays, so the next run is just as fast.
  3. -p 8080:80Connect port 8080 of your computer to port 80 inside the container, where Caddy listens. You choose the left number; the right one is fixed by the image.
  4. -v "$PWD":/usr/share/caddyShow the current folder ($PWD) inside the container as /usr/share/caddy — the folder the official image serves by default. Edit a file here and the next reload shows the change.
  5. caddy:2The image to run: the official Caddy image, any 2.x version. Without the :2 tag Docker takes latest, which may one day jump to a new major version under your feet.

Press Control-C to stop it. This is perfect for a quick look, but the command is long and forgotten the moment the terminal closes. For anything permanent you write it down in a file — which is the next project.

Project 1: an HTML homepage, from a folder to the internet

The whole project is three small files. index.html is the page, the Caddyfile tells Caddy what to serve, and compose.yaml tells Docker how to run Caddy. Docker Compose reads that file, so you never have to retype a long docker run command.

my-site
my-site/
├── compose.yaml
├── conf/
│   └── Caddyfile
└── site/
    └── index.html
compose.yaml
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./conf:/etc/caddy
      - ./site:/srv
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

compose.yaml line by line

  1. image: caddy:2The image to run — the same official Caddy image as before.
  2. restart: unless-stoppedIf the container crashes or the server reboots, Docker starts it again by itself, until you stop it on purpose.
  3. "80:80" · "443:443"Open the machine's ports 80 (http) and 443 (https) to Caddy. Caddy needs both to obtain a certificate and to send visitors from http to https.
  4. "443:443/udp"Port 443 again, but over UDP. It lets browsers use HTTP/3, the newest version of HTTP. Leaving it out is harmless; the site simply uses HTTP/2.
  5. ./conf:/etc/caddyYour conf folder becomes /etc/caddy, where Caddy looks for its Caddyfile. Caddy's documentation mounts the folder rather than the single file, because many editors replace a file when saving and a mounted single file would keep showing the old one.
  6. ./site:/srvYour site folder becomes /srv — the folder the Caddyfile serves. Change a page on disk and Caddy serves the new version at once, with no restart.
  7. caddy_data:/dataA named volume that Docker keeps for you. Caddy stores its HTTPS certificates and keys here, so they survive when the container is deleted and recreated. caddy_config beside it keeps Caddy's automatic settings the same way.

Try it on your own computer first

Write :80 in the address slot of the Caddyfile. It means "answer on port 80, whatever name is asked for" — plain http with no certificate, which is exactly right on a computer that has no domain name. Then start it and open http://localhost.

conf/Caddyfile
:80 {
	root * /srv
	file_server
}
terminal
cd my-site
docker compose up -d      # start Caddy in the background
open http://localhost     # or type the address into a browser
docker compose logs -f    # watch requests arrive (Control-C stops watching)

Putting it on the internet

  1. Rent a serverAny VPS with a public IP address will do — a small, cheap one is plenty. Install Docker on it with the script above, and make sure its firewall lets ports 80 and 443 in.
  2. Point your domain at itWhere you bought the , add a A record: name example.com, value the server's IP address. It can take from a few minutes to a few hours before the whole internet sees the change.
  3. Write the real addressIn the Caddyfile, replace :80 with your domain name. That one change is what switches on automatic HTTPS: Caddy requests a certificate, installs it, renews it and sends http visitors to https.
  4. Copy and startCopy the folder to the server with rsync, log in with ssh and start it with docker compose up -d. Watch the log: a line saying the certificate was obtained means your site is live.
conf/Caddyfile
example.com {
	root * /srv
	file_server
	encode gzip zstd
}

The address 203.0.113.10 below is a reserved example; use your own server's IP. To change the page later, edit index.html and run the same rsync again. The site folder is mounted into the container, so the new page is live on the next reload without restarting anything.

terminal
# on your computer: copy the project folder to the server
rsync -av my-site/ root@203.0.113.10:~/my-site/

# on the server: start it — Caddy fetches the HTTPS certificate by itself
ssh root@203.0.113.10
cd ~/my-site
docker compose up -d
docker compose logs -f caddy

Project 2: a React homepage

is a for building a page out of small reusable components. What matters for publishing is this: a React project is not served as it is. A build step turns it into plain HTML, CSS and JavaScript files in a dist folder — and from there it is the same job as Project 1.

You need Node.js on your computer for the commands below. Vite creates a React project that is ready to run, npm run dev shows it while you work, and npm run build writes the finished site.

terminal
# needs Node.js (nodejs.org) — create a React project with Vite
npm create vite@latest my-react -- --template react
cd my-react
npm install
npm run dev      # development server at http://localhost:5173

npm run build    # the finished site is written to dist/

Way A: build on your computer, copy the result

The simplest route reuses Project 1 as it is. Build, then copy only the dist folder into the server's site folder. --delete removes files from the previous build that no longer exist, so old leftovers do not pile up.

terminal
# way A: build here, copy only the result into Project 1's site folder
npm run build
rsync -av --delete dist/ root@203.0.113.10:~/my-site/site/
The one line React needs in the Caddyfile

A React app draws every page itself from one index.html. An address like /about is not a file on disk, so without help Caddy answers 404 the moment someone reloads that page or opens a shared link to it. try_files tells Caddy: serve the file if it exists, otherwise hand over index.html and let React draw the right page.

Caddyfile
example.com {
	root * /srv
	encode gzip zstd
	try_files {path} /index.html
	file_server
}

Way B: let Docker build it, with a two-stage Dockerfile

The sturdier route builds inside Docker, so the server — or a colleague's computer — always builds the same way, and nobody has to install Node on the server. The Dockerfile has two stages: the first builds with Node, the second keeps only the result.

Dockerfile
# stage 1: build the React app with Node
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# stage 2: only the finished files go into the Caddy image
FROM caddy:2
COPY Caddyfile /etc/caddy/Caddyfile
COPY --from=build /app/dist /srv

The Dockerfile line by line

  1. FROM node:24-alpine AS buildStage one starts from an image that has Node.js and names this stage build, so the next stage can take files from it.
  2. COPY package*.json ./ · RUN npm ciCopy only the package list first and install exactly the versions in the lock file. Docker reuses this step while the list stays the same, so later builds skip the slow install.
  3. COPY . . · RUN npm run buildCopy the rest of the source and build. The finished site is now in /app/dist inside the build stage.
  4. FROM caddy:2Stage two starts again from a clean Caddy image. Nothing from stage one comes along unless you copy it.
  5. COPY --from=build /app/dist /srvTake only the built files from the build stage and put them where the Caddyfile serves from. Node and node_modules stay behind, so the final image stays small.

Put the Caddyfile with try_files next to the Dockerfile and use the compose.yaml below: build: . means "make the image from this folder's Dockerfile" instead of downloading one. On the server, clone the code and one command builds and starts it; after each change, pull and run the same command again.

compose.yaml
services:
  web:
    build: .
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:
terminal
# keep node_modules and old builds out of the image
printf 'node_modules\ndist\n' > .dockerignore

# on the server: get the code, then build and start in one step
git clone https://github.com/you/my-react.git
cd my-react
docker compose up -d --build

# after you push a change: pull it and rebuild
git pull && docker compose up -d --build

The commands you will use

CommandWhat it does
docker compose up -dStart everything in compose.yaml in the background. Run it again after you change compose.yaml; only what changed is recreated.
docker compose psList this project's containers and whether they are running. The first thing to check when the site does not answer.
docker compose logs -fFollow the logs live. Certificate problems, wrong paths and crashes all show up here first. Control-C stops the watching, not the server.
docker compose exec -w /etc/caddy caddy caddy reloadMake Caddy reload its Caddyfile inside the running container without dropping a single connection. Use it after editing the Caddyfile.
docker compose downStop and delete the containers. Named volumes are kept, so the certificates survive. Adding -v would delete them too — avoid it.
docker compose pullDownload a newer Caddy image. Follow it with docker compose up -d to switch over to it.
Never throw away caddy_data

Your certificates live in the caddy_data volume. If it is deleted — with docker compose down -v, or by leaving the volume out of compose.yaml — Caddy has to request new certificates every time it starts. Certificate authorities limit how many certificates one domain can get in a week, so a few careless restarts can leave your site without HTTPS for days.

Web Client

You mark this yourself. Nothing is graded here.

Mark this step as understood?

This cannot be undone yet, so mark it only when you really understand.

Marked as understood

You mark this yourself. Nothing is graded here.

Guidelines

AI

AI Certification

Study

Vibe Coding

Game

Practice

Tutors

Chat

Forum

Create

Community

More

Glossary

Pick a word you don't know. You'll see a simple explanation and related terms.

No matching term yet.

Pick a term to see its explanation.