---
title: "Sa Vercel-a i GitHub-a na root server od 30 evra: sedam faza, za manje od sata"
date: 2026-08-02
author: "Damjan Savić"
canonical: https://damjan-savic.com/sr/znanje/vercel-to-root-server
language: sr
keywords: "Self-hosting, Infrastruktura, Docker, Claude Code, Next.js, Gitea"
video: https://www.youtube.com/watch?v=HAgdRl5VfOc
---
# Sa Vercel-a i GitHub-a na root server od 30 evra: sedam faza, za manje od sata

**Ukratko** — Sopstveni server sa osam namenskih jezgara i 16 GB ECC RAM-a košta 29,61 evro mesečno bez ugovorne obaveze i postavlja se za manje od sata. Sedam faza, a posle toga ceo lanac isporuke je moj umesto da bude raspoređen na dve kompanije nad kojima nemam nikakvu kontrolu.

---

Jedan server, osam namenskih procesorskih jezgara, 16 gigabajta ECC RAM-a, sopstveni git hosting, sopstveni hosting sajta i Claude Code koji radi direktno na mašini. 29,61 evro mesečno, bez ugovorne obaveze, postavljeno za manje od sata.

Pre toga je moj portfolio radio na Vercel-u, a kod je stajao na GitHub-u. Oboje radi dobro. Ali mi je lanac isporuke tako bio raspoređen na dve kompanije nad kojima nemam nikakvu kontrolu. Vercel Pro je 20 dolara mesečno čim izađeš iz hobi granica, GitHub još 4 do 21 dolar zavisno od plana. A ako bilo koji od njih promeni cene, uslove ili ograničenja za build, ja to saznam mejlom.

Postoji i drugi razlog, koji manje zvuči kao princip a više znači u svakodnevnom radu: kad god hoću nešto da isprobam na pravom serveru, treba mi pravi server.

## Mašina

NetCup RS 2000 G12:

| | |
| --- | --- |
| Procesor | AMD EPYC 9645, 8 namenskih jezgara — ne deljenih |
| RAM | 16 GB DDR5 ECC |
| Disk | 512 GB NVMe na hardverskom RAID kontroleru |
| Saobraćaj | Flat rate |
| Lokacija | Nirnberg, Nemačka |
| Cena | 29,61 evro mesečno sa 19 posto nemačkog PDV-a |

Dve poštene napomene o ceni. Prvo, uzimam period od mesec dana: mesec dana minimalno, mesečna naplata, otkazivanje kad god hoću. Ko se obaveže na dvanaest meseci dobija 17 posto popusta — oko 25 evra mesečno — ali se naplaćuje za godinu unapred, otprilike 300 evra odjednom. Tih pet evra razlike mesečno plaćam za slobodu.

Drugo, prve porudžbine imaju garanciju zadovoljstva od 30 dana. Ako kreneš za mnom i ne svidi ti se, novac se vraća.

Lokaciju vredi pažljivo pročitati: „Evropa bez preferencije" je oko četiri evra jeftinije, ali onda mašina završi u Austriji, Nemačkoj ili Holandiji i nemaš izbor. Ja sam hteo Nirnberg, pa plaćam Nirnberg.

## Faza 1 · Porudžbina

Nakon porudžbine se većina saplete o istu stvar: NetCup ima **dva** panela, sa odvojenim pristupnim podacima, i oba stižu mejlom.

- **CCP**, korisnički panel: ugovori, računi, nadogradnje.
- **SCP**, serverski panel: sama mašina.

Treba ti SCP. Pod „Servers" izabereš mašinu, odeš na medije/imidže, izabereš Ubuntu 22.04.5, na stranici sa podešavanjima tražiš da se napravi dodatni korisnik i pokreneš instalaciju. Na kraju NetCup prikaže root lozinku. Kopiraj je — treba ti tačno za jedan minut i nikada više.

Još dve stvari pre nego što se krene dalje, i obe štede vreme kasnije:

**Snapshot čiste instalacije.** Faza 3 menja SSH konfiguraciju, a ako to pođe naopako, hoću povratak od trideset sekundi umesto ponovne instalacije.

**DNS zapis, odmah.** A zapis za poddomen — kod mene `git.damjan-savic.com` — na novu IP adresu servera, sa najmanjim mogućim TTL-om, dakle jedan minut. Portfolio u tom trenutku još radi na Vercel-u i ne želim prekid. Propagacija DNS-a traje, pa je pokrećem i puštam da radi u pozadini dok radim sve ostalo.

## Faza 2 · Korisnik

Prva prijava kao `root` sa lozinkom iz faze 1. Raditi kao root je loša navika, pa odmah nastaje pravi korisnik, sa lozinkom i `sudo` pravima.

Zatim SSH ključ. Za razliku od nekih provajdera, NetCup ne ubacuje tvoj javni ključ pri porudžbini — kreće se sa lozinkom. To se sada ispravlja.

```bash
# Na sopstvenom računaru, ako ključ još ne postoji
ssh-keygen -t ed25519 -C "damjan@coderconda"

# Na serveru
mkdir -p ~/.ssh && chmod 700 ~/.ssh
sudo apt update && sudo apt install nano
nano ~/.ssh/authorized_keys        # nalepi javni ključ kao jedan red
chmod 600 ~/.ssh/authorized_keys
```

Javni ključ ostaje na serveru, privatni na mom računaru.

A onda deo koji zaista menja svakodnevni rad: ne želim više da kucam IP adresu. Na sopstvenom računaru — na Windows-u u `C:\Users\<ime>\.ssh\config` — dodaje se blok:

```text
Host coderconda
  HostName 203.0.113.10
  User damjan
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes
```

Posle toga je cela prijava jedna reč:

```bash
ssh coderconda
```

## Faza 3 · Očvršćavanje

Ova mašina ima javnu IP adresu i prijavu lozinkom koja radi. Otprilike deset minuta nakon puštanja uživo, botovi počinju da kucaju na port 22. To nije paranoja, to je prosto internet.

### SSH

```bash
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
```

```text
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
AllowUsers damjan
```

Root prijava isključena, prijava lozinkom potpuno isključena, jedan jedini korisnik pušten unutra. Pre restarta proveri sintaksu:

```bash
sudo sshd -t
sudo systemctl restart ssh
```

**Ne zatvaraj ovaj terminal.** Otvori drugi prozor i tamo testiraj prijavu. Ako radi, sve je u redu. Ako ne radi, još uvek imaš otvorenu sesiju da povučeš izmenu. A ako ipak zatvoriš oba, imaš snapshot iz faze 1 i udaljenu konzolu u SCP-u — ona radi i kada SSH ne radi.

Meni se tačno to i desilo: u `AllowUsers` mi je stajalo pogrešno ime i zaključao sam se. Drugi prozor postoji upravo zbog toga.

### Zaštitni zid

Ovde je stvarna razlika u odnosu na neke druge provajdere: NetCup ti **ne** daje odvojen zaštitni zid na mrežnom nivou koji se sklopi klikovima. Tvoj zaštitni zid je onaj na mašini. Ovaj korak ovde dakle nije opcion.

```bash
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```

Tri porta otvorena, sve ostalo zatvoreno.

### fail2ban

```bash
sudo apt install fail2ban
sudo systemctl enable --now fail2ban
```

Podešeno na jedan sat zabrane nakon tri neuspela pokušaja prijave. Šta se desilo u videu: fail2ban je zabranio IP adresu mog sopstvenog računara, pa sam morao kroz SCP konzolu da je odblokiram. I to je argument za udaljenu konzolu.

### Auto-ažuriranja

```bash
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
```

### DDoS zaštita

Zaštita od DDoS napada uključena je na svakom NetCup root serveru, bez doplate, sa kapacitetom filtriranja u rangu od dva terabita u sekundi. To je mrežni nivo. HTTP nivo dolazi kasnije, u fazi 7.

## Faza 4 · Claude Code

To je deo koji sve ostalo čini brzim. Umesto da nginx konfiguracije pišem rukom, instaliram Claude Code na server i pustim ga da radi.

```bash
# posle instalacije proveri da li je claude u PATH-u
which claude
claude --version
```

Na serveru bez grafičkog okruženja nema pregledača. Prijava zato ispiše adresu koju otvoriš na sopstvenom računaru, gde si ionako prijavljen, a vraćeni kod nalepiš u terminal.

Detalj koji spada u sliku: usred te faze vratio se `529 overloaded`. Prema statusnoj stranici, u tom trenutku su postojale povišene stope grešaka na svim modelima, poznate već oko dva sata. Dakle pauza, a posle je isti prompt prošao. Ko planira migraciju treba da računa i na to da alata u lancu povremeno nema.

## Faza 5 · Gitea

Gitea je samostalno hostovan git servis. GitHub, ali tvoj, i radi u oko 200 megabajta RAM-a. Uzimam Docker jer je najbrži put unutra i najčistiji put nazad.

U compose fajlu stoji najvažniji red celog videa:

```yaml
ports:
  - '127.0.0.1:3000:3000'
```

Ne `3000:3000`. Razlog: **Docker piše sopstvena iptables pravila i time prolazi pored UFW-a.** Ko port objavi na uobičajen način, tog zaštitni zid ne zaustavlja. A na ovom serveru je UFW jedini zaštitni zid koji postoji — pa ovaj jedan red ovde znači više nego bilo gde drugde. Vezivanje za localhost znači da mu može prići samo nginx na istoj mašini.

Zatim nginx i certbot, i tu puštam Claude Code da radi:

```text
Instaliraj nginx i certbot. Napravi nginx sajt za git.damjan-savic.com koji
kao reverse proxy pokazuje na 127.0.0.1:3000, sa client_max_body_size 512M
i uobičajenim proxy zaglavljima. Aktiviraj ga, proveri konfiguraciju,
ponovo učitaj nginx.
```

Tu se pokazalo jedno ograničenje: `sudo` u tom okruženju ne može da traži lozinku. Komande za instalaciju su morale da se puste u pravom terminalu; posle toga je Claude Code nastavio, proverio instalaciju i napisao konfiguraciju. Usput sam koristio i ChatGPT da komande formatira u jedan red — i to spada u poštenu verziju.

Zatim sertifikat:

```bash
sudo certbot --nginx
```

Tu se isplati faza 1: DNS zapis od ranije je u međuvremenu propagiran, pa izazov prolazi odmah. Posle toga otvoriš `git.damjan-savic.com` u pregledaču, potvrdiš SQLite kao bazu, putanje ostaviš kakve jesu, postaviš naslov sajta i napraviš administratorski nalog. Sopstveni git server, gotovo.

## Faza 6 · Selidba

Gitea nosi alat za migraciju, pa je ovo lakše nego što zvuči. Treba ti GitHub token (Settings → Developer Settings → Personal Access Tokens → Tokens (classic)), pa u Gitea: „New Migration", izabereš GitHub, uneseš token, postaviš vlasnika i ime repozitorijuma, čekiraš issues, labele i izdanja.

Ono što stigne nije kopija poslednjeg stanja, nego repozitorijum: puna istorija commit-a, sve grane, svi issues.

U tome postoji jedna odluka: polje „This repository will be a mirror". Ako ga čekiraš, GitHub ostaje izvor istine, a Gitea samo prati. Ja sam ga ostavio prazno, jer Gitea odsad treba da bude primarna.

Dakle na sopstvenom računaru:

```bash
git remote set-url origin https://git.damjan-savic.com/damjan/portfolio.git
git pull --rebase
git push
```

Prvi push je odbijen — `fetch first`. Dakle prvo povuci, pa gurni. Posle toga moj računar gura na moj sopstveni server.

## Faza 7 · Uživo

Kloniranje na serveru, instalacija, build:

```bash
cd ~ && git clone https://git.damjan-savic.com/damjan/portfolio.git
cd portfolio
corepack prepare pnpm@latest --activate
pnpm install --frozen-lockfile
pnpm build
```

Popravke je tražila konfiguracija koja je bila usmerena na Vercel — projekat koji se tamo gradio nosi u sebi pretpostavke koje na goloj mašini više ne važe.

Zatim radi kao Node proces iza nginx-a, kojim upravlja PM2, na portu 3001 — 3000 je zauzeo Gitea:

```bash
pm2 start .next/standalone/server.js --name portfolio
pm2 save
pm2 logs portfolio
```

I opet nginx, opet preko Claude Code:

```text
Napravi nginx sajt za damjan-savic.com i www.damjan-savic.com koji proksira
na 127.0.0.1:3001. Dodaj limit_req_zone u http blok sa 20 zahteva u sekundi
i primeni ga u location bloku sa burst=40 nodelay. Dodaj gzip i uobičajena
bezbednosna zaglavlja.
```

Claude je napisao konfiguraciju i tri stavke izričito vratio kao otvorene odluke umesto da ih prećutno donese: nema TLS bloka, jer ga nisam tražio; ograničenje brzine pokriva i statičke fajlove pod `/_next/static`; i preusmerenje između `www` i osnovnog domena još nedostaje, oba imena trenutno serviraju isti sadržaj.

To je ograničenje brzine na nivou aplikacije. NetCup poplavu hvata na mrežnom nivou, ovo hvata HTTP poplavu. Ozbiljan botnet neće zaustaviti, ali jednu skriptu koja udara po serveru hoće.

### Prebacivanje

To je jedini trenutak u kojem je sajt zaista na kocki, pa ide brzo. Kod DNS provajdera:

1. Obriši A zapis koji pokazuje na Vercel.
2. Obriši `www` CNAME koji pokazuje na `cname.vercel-dns.com`.
3. Dodaj A zapis na IP servera.
4. Dodaj drugi A zapis za `www` na istu adresu.

Zatim čekaj i proveravaj umesto da nagađaš:

```bash
dig damjan-savic.com +short
```

Kod mene je `www` već bio propagiran dok je goli domen još pokazivao na Vercel — jedan stari zapis koji sam prevideo. Nekoliko minuta kasnije i on je bio ispravan. Onda još jednom certbot, i sajt stoji: moj server, moj sertifikat, moja git istorija. Projekat na Vercel-u može da ode.

## Četiri kvara

1. **Zaključao si se preko SSH-a.** Otvori SCP i koristi udaljenu konzolu — radi i kada SSH ne radi. Ako ni to ne pomogne, vrati snapshot iz faze 1.
2. **certbot pada sa greškom u izazovu.** DNS ti još nije propagiran. Proveri sa `dig`, sačekaj, pusti ponovo. Ništa drugo nije pokvareno.
3. **Docker kontejner je dostupan sa interneta iako UFW kaže da je port zatvoren.** To je ona iptables stvar odozgo. Proveri svaki `ports` unos u svakom compose fajlu: mora da počinje sa `127.0.0.1:`.
4. **Ograničavanje saobraćaja.** Flat rate je stvaran, ali preko otprilike 3 TB u 24 sata dobiješ ograničenje na 300 megabita. Za portfolio sajt se to nikada neće desiti.

## Obrazac

Kada ga jednom vidiš, sve ostalo može na isti server. Struktura je uvek ista:

1. Pusti servis na lokalnom portu
2. Veži ga za `127.0.0.1`
3. Stavi nginx ispred kao reverse proxy
4. Usmeri poddomen na njega
5. Pusti certbot

Gitea na 3000, portfolio na 3001, sledeća stvar na 3002.

Isti pet koraka svaki put. Sa 16 gigabajta RAM-a ima mesta na pretek. A svaki od tih koraka je nešto što Claude Code može da napiše umesto tebe, čim mu kažeš port i domen.

![Dijagram · Pet koraka koje ponavlja svaki sledeći servis na istoj mašini](/media/website/article/figures/vercel-to-root-server-pattern.avif)

## Šta nedostaje

Iskreno: automatsko postavljanje. Trenutno još moram ručno da povučem i ponovo izgradim. Gitea webhook i shell skripta od pet redova to rešavaju. Posle toga su očigledni naredni koraci Uptime Kuma za nadzor, automatski snapshot-ovi, n8n, Postgres instanca ili Cloudflare ispred.

## Zaključak

Self-hosting više nije težak. Samo je nenaviknut. Sedam faza, jedan server, 30 evra mesečno, bez ugovorne obaveze — i ceo lanac je moj.

## Poglavlja snimka

- [0:48 — Mašina i cena](https://youtu.be/HAgdRl5VfOc?t=48)
- [2:23 — Faza 1: porudžbina, Ubuntu, snapshot, DNS](https://youtu.be/HAgdRl5VfOc?t=143)
- [4:44 — Faza 2: korisnik i SSH ključ](https://youtu.be/HAgdRl5VfOc?t=284)
- [7:57 — Faza 3: očvršćavanje, zaštitni zid, fail2ban](https://youtu.be/HAgdRl5VfOc?t=477)
- [11:11 — Faza 4: Claude Code na serveru](https://youtu.be/HAgdRl5VfOc?t=671)
- [12:49 — Faza 5: Gitea, Docker i red sa 127.0.0.1](https://youtu.be/HAgdRl5VfOc?t=769)
- [18:28 — Faza 6: selidba repozitorijuma](https://youtu.be/HAgdRl5VfOc?t=1108)
- [20:53 — Faza 7: build, PM2, nginx, prebacivanje DNS-a](https://youtu.be/HAgdRl5VfOc?t=1253)
- [27:15 — Četiri načina da pukne](https://youtu.be/HAgdRl5VfOc?t=1635)

## Česta pitanja

### Zašto u compose fajlu mora da stoji 127.0.0.1:3000:3000?

Zato što Docker piše sopstvena iptables pravila i time zaobilazi UFW. Ako port objaviš na uobičajen način, tvoj zaštitni zid ga ne zaustavlja — a na ovom serveru je UFW jedini zaštitni zid koji postoji. Vezivanje za localhost znači da mu prilazi samo nginx, sa iste mašine.

### Kako da se pri kaljenju SSH-a ne zaključaš spolja?

Ne zatvaraj terminal. Posle ponovnog pokretanja SSH-a otvori drugi prozor i tamo isprobaj prijavu; ako ne prođe, u još otvorenoj sesiji možeš da vratiš izmenu. Iza toga stoje snimak stanja sa čiste instalacije i udaljena konzola u SCP-u, koja radi i kada SSH ne radi. Meni se to i desilo — pogrešno ime u AllowUsers.

### Koji je šablon za svaki sledeći servis na istoj mašini?

Pet koraka, svaki put isti: pusti servis na lokalnom portu, veži ga za 127.0.0.1, stavi nginx ispred kao obrnuti proksi, uperi poddomen na njega, pokreni certbot. Gitea na 3000, portfolio na 3001, sledeće na 3002. Svaki od tih koraka je nešto što Claude Code može da napiše čim mu kažeš port i domen.

---

Sa Vercel-a i GitHub-a na root server od 30 evra: sedam faza, za manje od sata — https://damjan-savic.com/sr/znanje/vercel-to-root-server
Sadržaj generisan veštačkom inteligencijom: https://damjan-savic.com/sr/transparentnost-vi
© 2026 Damjan Savić. https://damjan-savic.com
