Parte 4 - VPS și deploy
În sfârșit ce contează: cum ajunge codul de pe laptop pe un server, accesibil din toată lumea, cu HTTPS valid.
Ce e un VPS
VPS = Virtual Private Server. O mașină virtuală care rulează pe un server fizic al unui provider, dar pe care aveți control complet (root SSH, instalați ce vreți, alegeți OS-ul). Diferit de:
| Tip | Control | Preț | Când alegeți |
|---|---|---|---|
| Shared hosting | aproape niciun control (panou cPanel) | foarte ieftin | site static / WordPress |
| VPS | root, OS la alegere | ~5-15 EUR/luna | proiecte mici-medii, deploy-uri custom |
| Dedicated server | hardware fizic propriu | ~50-200 EUR/luna | aplicații cu cerințe resource mari |
| Cloud (AWS, GCP, Azure) | instanțe elastice + N servicii | pay-per-use, scalabil | producție cu trafic variabil |
Pentru proiectul de DAW, VPS-ul e fix ce vă trebuie.
Cum alegeți un VPS
Provideri uzuali cu raport preț/performanță decent:
| Provider | Note |
|---|---|
| Hetzner | DE-based, billing orar, plan-uri x86 și ARM (CAX = ARM), API + UI bune |
| Netcup | DE-based, ieftin, contract minim 1 lună, mai mult disk per euro |
| Contabo | Mai mult disk și RAM la preț mic, dar CPU oversubscribed (bursty) |
| OVH / Kimsufi | FR-based, plan-uri entry-level OK |
| DigitalOcean / Vultr / Linode | US/UE, plan-uri standard, UI prietenoasă pentru beginner |
| Romarg | RO-based, scump față de Hetzner/Netcup la spec similar, dar suport în română + datacenter în țară |
Locații relevante (latency din România):
- Frankfurt / Falkenstein / Nurnberg (DE) - 30-40ms
- Vienna (AT), Helsinki (FI) - 25-35ms
- București (RO) - 5-10ms (dar oferte mai limitate / mai scumpe)
Specs suficiente pentru un proiect tip .NET API + DB + frontend static:
- 1-2 vCPU, 2 GB RAM, 20-40 GB SSD - perfect. Docker, Postgres și .NET runtime stau confortabil aici.
- 4 GB RAM + - dacă ai mai multe servicii sau procese de build mai grele pe același server.
Containerele sunt ușoare: un .NET runtime alpine + Postgres mic + un nginx ocupă împreună ~300-400 MB RAM în regim normal. Lab-ul vostru rulează fără probleme pe un entry-level de 5 EUR/luna.
Trafic: 1-20 TB/lună inclus la majoritatea providerilor - mult mai mult decât vă trebuie.
Setup bază Ubuntu
După ce comandați VPS-ul, primiți:
- IP public (în lab-ul nostru
178.105.43.73, VPS-B Hetzner pentrustudent-dev.ro) - root SSH (cu parolă sau key)
Primii pași:
1. SSH key only, no password
# Pe local (laptop), generati o cheie SSH daca nu aveti deja
ssh-keygen -t ed25519 -C "your-email@example.com"
# Output: ~/.ssh/id_ed25519 (privata) si id_ed25519.pub (publica)
# Copiati publica pe server
ssh-copy-id root@178.105.43.73 # sau editati manual ~/.ssh/authorized_keys
# Login fara parola
ssh root@178.105.43.73
Pe server, edit /etc/ssh/sshd_config:
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
sudo systemctl reload sshd. Acum nu mai poate nimeni intra cu parolă - doar cu cheie privată.
2. User non-root
adduser admin # creeaza user, seteaza parola
usermod -aG sudo admin # poate rula sudo
mkdir -p /home/admin/.ssh
cp ~/.ssh/authorized_keys /home/admin/.ssh/
chown -R admin:admin /home/admin/.ssh
chmod 700 /home/admin/.ssh
chmod 600 /home/admin/.ssh/authorized_keys
Login ca admin@178.105.43.73 de acum (sau, după configurarea DNS-ului, admin@student-dev.ro). Niciodată nu lucrați ca root direct - cu sudo doar când aveți nevoie.
3. UFW firewall
sudo apt update && sudo apt install -y ufw
sudo ufw default deny incoming # blocheaza tot inbound
sudo ufw default allow outgoing # permite outbound (altfel apt fail-uieste)
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (pentru certbot HTTP-01 + redirect)
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
sudo ufw status verbose
4. fail2ban (anti brute-force)
sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban
Default config protejează SSH. Dacă cineva încearcă să se logheze de 5 ori cu parolă greșită (chiar dacă e dezactivată, scan-urile încearcă), IP-ul e banat 1 oră.
5. Docker
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker admin # admin poate rula docker fara sudo
# Reconnect SSH ca sa intre in vigoare
exit && ssh admin@178.105.43.73
docker --version
6. Updates automate (security patches)
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Cumpărat domeniu și configurat DNS
- Mergeți pe registrar (Spaceship/Cloudflare/Namecheap/Romarg), cumpărați domeniul vostru (
myapp.dev,prenume-nume.ro, etc., ~5-12 EUR/an). În lab-ul nostru domeniul cursului estudent-dev.rola Romarg. - În panoul de DNS, configurați:
A student-dev.ro 178.105.43.73
A *.student-dev.ro 178.105.43.73 (wildcard pentru subdomenii voastre)
Așteptați ~5-30 min pentru propagare. Verificați:
dig +short student-dev.ro
# 178.105.43.73
dig +short newsportal.student-dev.ro
# 178.105.43.73 (wildcard prinde orice subdomeniu)
Două căi de deploy
Path 1: SSH manual + rsync
Cea mai educațională, “feel the server”:
# Local (din folder-ul cu codul) - cu user-ul vostru de pe VPS-ul cursului (ex: ziarist)
rsync -az --delete \
--exclude='.git' --exclude='bin' --exclude='obj' --exclude='node_modules' \
./ ziarist@student-dev.ro:~/apps/
# SSH si build
ssh ziarist@student-dev.ro
cd ~/apps
docker compose up -d --build
# Logs
docker compose logs -f api
Sau și mai simplu, dacă cwRsync nu vă merge pe Windows, folosiți scp:
scp -r ./backend ziarist@student-dev.ro:~/apps/
# parola la prompt (cea primita pe email)
Pro: simplu, înțelegeți exact ce se întâmplă. Contra: manual la fiecare modificare; dacă aveți 10 commit-uri, faceți 10 deploys.
Path 2: GitHub Actions
Workflow automat: git push -> GH Actions -> SSH la server -> rebuild containere.
.github/workflows/deploy.yml:
name: Deploy to VPS
on:
push:
branches: [main]
workflow_dispatch: # permite rulare manuala din UI
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/key
chmod 600 ~/.ssh/key
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Sync code
run: |
rsync -az --delete \
--exclude='.git' --exclude='.github' \
--exclude='bin' --exclude='obj' --exclude='node_modules' \
-e "ssh -i ~/.ssh/key" \
./ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:~/apps/
- name: Rebuild on server
run: |
ssh -i ~/.ssh/key ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
"cd ~/apps && docker compose up -d --build"
În Settings > Secrets and variables > Actions aveți nevoie de:
SSH_HOST=student-dev.ro(sau domeniul vostru)SSH_USER= username-ul vostru (ex:ziarist)SSH_PRIVATE_KEY= conținutul lui~/.ssh/id_ed25519(cheia privată)
Pentru VPS-ul cursului: studenții au autentificare cu parolă (Match Group students), nu cu cheie - GitHub Actions necesită o ajustare. Cel mai simplu pentru lab: rămâneți pe SSH manual + rsync (Path 1).
NICIODATĂ nu commit-ați SSH_PRIVATE_KEY în repo - doar în secrets.
Diferențe dev vs prod
| Aspect | Dev local | Prod VPS |
|---|---|---|
| Cert TLS | self-signed | Let’s Encrypt |
| Image tags | :latest (acceptabil) |
pin la versiune (:1.2.3 sau digest) |
| Secrets | .env local |
env vars la runtime / vault |
| Logs | stdout în compose | log driver json + rotation |
| Restart | manual | restart: unless-stopped (deja în compose) |
| Healthchecks | opțional | obligatoriu |
| Backup DB | nu există | weekly pg_dump cu cron |
| Updates | manual când aveți timp | unattended-upgrades pe OS, dependabot pe NuGet/npm |
| Resource limits | nu | deploy.resources.limits per service |
Ce am evitat în lab dar contează în producție
- Container hardening:
read_only: true,cap_drop: [ALL],security_opt: [no-new-privileges]. Mai mult în OWASP Container Security. - Image scanning:
docker scout cves <image>sau Trivy. Vedeți câte CVE-uri are imaginea înainte de deploy. - Container egress filtering: blochează outbound spre porturi neautorizate (anti spam relay).
- Niciodată
--privileged, niciodată-v /var/run/docker.sock:/var/run/docker.sock(= root pe host).
În secțiunea 06 (deploy proiect) aveți pași concreti pentru deploy-ul proiectului pe VPS-ul cursului.
Demo live
În sesiunea live vedeți:
- VPS-ul cursului wipe + rebuild de la zero pe Hetzner (Ubuntu 24.04, doar IP + parolă inițială).
- Eu configurez serverul de la zero în fața voastră: SSH key auth,
adminuser cu sudo, dezactivare root login, UFW (firewall), fail2ban (anti brute-force), SSH hardening, swap, hostname, Docker, stack-ulstudlab(nginx + certbot + Postgres + Adminer), cert-uri Let’s Encrypt HTTP-01. - Cont demo
ziarist(parolăziarist, accesibil tuturor) provisionat live, cu homechmod 750, grupuristudents+docker, sudo whitelist limitat laapt-get install/update/upgrade. Demonstrez ce poate și ce NU poate să facă. - Deploy News Portal ca ziarist: scp sursă de pe laptop -> server, editare
docker-compose.yml+.env,docker compose up -d --build. - Verify live:
https://newsportal.student-dev.ro/api/articlescu cert Let’s Encrypt valid +https://db.student-dev.ro(Adminer, vede DB-ul lui ziarist). - Bulk provision live: scriptul
provision_user.ps1 -FromFile users_new.csvcreează ~30 conturi pe server în 30 secunde + scriptulsend_provision_emails.py --sendtrimite credențialele via Resend la cei înscriși.
După demo, cei care au optat pentru deploy primesc un email cu credențialele lor. Secțiunea 06 vă ghidează prin pașii voștri concreți.