Logo

~/saidqb cat wiki/stack/server/deploy-fastapi.md

MDdeploy-fastapi.md

commit by saidqb

Deploy FastAPI (Uvicorn/Gunicorn + Nginx + systemd)

Urutan setup service Python di server, dari nol sampai bisa diakses lewat domain. Tiap project dapat Linux user sendiri (/home/<appuser>/app), bukan numpuk di satu folder /var/www bersama — kalau server ini dipakai buat banyak project, satu project ke-compromise tidak otomatis bisa baca file project lain.

0. Prasyarat sebelum deploy

  • Akses SSH ke server (user dengan hak sudo) — disiapkan penyedia server, bukan sesuatu yang di-install
  • Domain sudah diarahkan (DNS A record) ke IP server — diatur di panel DNS registrar, bukan di server
  • Python 3 + venv + pip: sudo apt install python3 python3-venv python3-pip -y (cek: python3 --version)
  • Nginx sudah terinstall: sudo apt install nginx -y
  • Git terinstall di server: sudo apt install git -y (dipakai untuk git pull tiap deploy)
  • Text editor di server buat edit systemd unit/config, minimal nano atau vim — server diakses lewat SSH, tidak ada GUI editor: sudo apt install vim -y

1. Buat user khusus untuk app

sudo adduser --disabled-password --gecos "" fastapiapp

--disabled-password — user ini tidak bisa login pakai password (cuma diakses lewat sudo -iu fastapiapp dari user SSH yang sudah masuk). --gecos "" skip pertanyaan nama/nomor telepon dsb yang tidak relevan buat service account. Home directory-nya otomatis jadi /home/fastapiapp.

2. Ambil kode & siapkan environment (jalan sebagai fastapiapp)

sudo -iu fastapiapp
git clone <url-repo-git> app
cd app
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install gunicorn uvicorn
exit                       # kembali ke user SSH biasa

sudo -iu fastapiapp masuk sebagai user itu dengan $HOME=/home/fastapiapp, jadi git clone ... app otomatis jadi /home/fastapiapp/app — seluruh isinya (termasuk venv/) otomatis kepemilikan fastapiapp.

3. Buat systemd service

/etc/systemd/system/app.service

[Unit]
Description=FastAPI app
After=network.target

[Service]
User=fastapiapp
Group=fastapiapp
WorkingDirectory=/home/fastapiapp/app
ExecStart=/home/fastapiapp/app/venv/bin/gunicorn main:app -k uvicorn.workers.UvicornWorker -w 4 -b 127.0.0.1:8000
Restart=always

[Install]
WantedBy=multi-user.target

Karena service jalan sebagai fastapiapp — pemilik asli semua filenya — tidak perlu chown/chmod tambahan seperti kalau service jalan sebagai www-data yang beda user dari yang nge-clone kodenya.

sudo systemctl daemon-reload
sudo systemctl enable app
sudo systemctl start app

4. Reverse proxy Nginx

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo ufw allow 'Nginx Full'   # buka port 80/443 di firewall — tanpa ini situs tidak bisa diakses dari luar meski Nginx sudah jalan benar, kalau ufw aktif (default di banyak VPS). Port 8000 tidak perlu dibuka karena cuma diakses lewat proxy_pass internal.

5. Urutan tiap kali deploy update

sudo -iu fastapiapp
cd app
git pull origin production          # lihat catatan git subtree kalau pakai struktur doc+project
source venv/bin/activate
pip install -r requirements.txt     # cuma perlu kalau ada dependency baru
exit
sudo systemctl restart app

restart (bukan reload) karena kode Python di-load ke memori saat proses pertama kali start — sama seperti kasus queue worker Laravel, proses lama tidak otomatis baca perubahan kode. systemctl restart perlu sudo dari user biasa (bukan fastapiapp, yang tidak punya akses sudo), makanya baris ini di luar blok sudo -iu fastapiapp.

Opsional: HTTPS

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d api.example.com

Konfigurasi SSL-nya tidak perlu ditulis manual — plugin --nginx otomatis suntik langsung ke server block yang sudah dibuat di Langkah 4 (listen 443 ssl, path sertifikat, redirect HTTP→HTTPS), dan pasang auto-renewal sendiri (sertifikat Let's Encrypt berlaku 90 hari, diperpanjang otomatis sebelum expired). Hasilnya kira-kira begini:

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparam.pem; # managed by Certbot
}

server {
    if ($host = api.example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    server_name api.example.com;
    return 404; # managed by Certbot
}

Kalau nanti edit config ini lagi, jangan hapus baris # managed by Certbot — dan tetap sudo nginx -t && sudo systemctl reload nginx tiap habis edit.

Kalau domain dipasang di belakang Cloudflare (proxy oranye), lihat catatan cloudflare.md — ada penyesuaian mode SSL/TLS & opsi sertifikat yang perlu diperhatikan.