Kamu masih deploy aplikasi Python ke VPS pakai python app.py & atau nohup di dalam tmux? Bersiaplah dibangunkan tengah malam karena prosesnya mati dan tidak ada yang menghidupkan lagi. systemd menangani itu: ia menjalankan aplikasimu sebagai service, menyalakannya ulang ketika crash, dan memastikannya langsung jalan begitu server boot.

Kenapa Tidak Pakai nohup atau tmux?

nohup dan tmux memang cepat, tapi rapuh. Kalau proses mati karena exception yang tidak tertangkap atau server kehabisan memori, kamu harus login manual dan menjalankannya lagi. Kalau VPS reboot, aplikasimu ikut mati sampai kamu sadar. Log pun tersebar di file sembarang tanpa timestamp yang rapi. systemd memberi satu tempat untuk start, stop, status, dan log. Docker memang bisa dipakai untuk kasus yang sama, tapi kalau kamu tidak butuh container, systemd jauh lebih ringan dan tidak menambah lapisan abstraksi.

Siapkan Aplikasi Contoh

Kita pakai FastAPI karena ringan dan cepat. Kalau belum pernah menyentuh FastAPI, baca dulu panduan FastAPI dari nol. Buat direktori proyek dan virtual environment:

bash
mkdir -p ~/apps/myapp
cd ~/apps/myapp
python3 -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn

Lalu simpan file app.py dengan isi seperti ini:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def health():
    return {"status": "ok"}


@app.get("/ping")
def ping():
    return {"message": "pong"}

Jalankan manual dulu untuk memastikan semuanya beres. Buka browser atau pakai curl ke http://127.0.0.1:8000/ping.

bash
uvicorn app:app --host 127.0.0.1 --port 8000

Kalau muncul {"message":"pong"}, tekan Ctrl+C dan lanjut. Sekarang kita ubah proses ad-hoc ini menjadi service.

Buat Unit File systemd

Unit file adalah file teks berisi instruksi cara systemd menjalankan aplikasimu. Buat file myapp.service seperti di bawah. Ganti hermes dengan nama user di servermu.

bash
[Unit]
Description=Aplikasi FastAPI contoh saya
After=network.target

[Service]
User=hermes
Group=hermes
WorkingDirectory=/home/hermes/apps/myapp
ExecStart=/home/hermes/apps/myapp/.venv/bin/uvicorn app:app --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target

Salin ke direktori systemd, reload daemon, lalu aktifkan dan jalankan sekaligus:

bash
sudo cp myapp.service /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl enable --now myapp

Periksa statusnya. Kalau baris Active: menunjukkan running, service-mu sudah hidup.

bash
sudo systemctl status myapp

Memahami Isi Unit File

Beberapa baris di atas punya peran spesifik. After=network.target memastikan service baru mulai setelah jaringan siap. User dan Group menentukan akun yang menjalankan proses — jangan pakai root kalau tidak perlu, itu memperluas dampak kalau aplikasimu kena serangan. ExecStart adalah perintah yang dieksekusi dan harus berupa path absolut, makanya kita tulis path lengkap ke binary uvicorn di dalam venv, bukan sekadar uvicorn. Restart=on-failure berarti systemd hanya menyalakan ulang saat proses keluar dengan status error, bukan saat kamu berhentikan manual. WantedBy=multi-user.target menempelkan service ke target boot multi-user, sehingga ia ikut menyala setiap server reboot.

Bedanya enable dan start. start menjalankan service sekarang juga. enable mendaftarkan service agar otomatis start saat boot. Keduanya bisa digabung dengan enable --now. Service yang hanya di-start tanpa di-enable akan hilang begitu server reboot.

Perintah yang Kamu Pakai Setiap Hari

bash
sudo systemctl status myapp
sudo systemctl stop myapp
sudo systemctl start myapp
sudo systemctl restart myapp
sudo systemctl disable myapp

Setiap kali kamu mengubah isi myapp.service, jalankan sudo systemctl daemon-reload dulu sebelum restart, supaya systemd membaca ulang file unit tersebut.

Baca Log Tanpa Mencari File

systemd menangkap stdout dan stderr aplikasimu ke journal. Tidak perlu menulis log ke file sendiri. Opsi -f mengikuti log secara real-time, mirip tail -f.

bash
sudo journalctl -u myapp -f
sudo journalctl -u myapp --since "10 minutes ago"
sudo journalctl -u myapp -n 50 --no-pager

Kalau kamu sudah nyaman dengan Bash dan CLI Linux, tiga perintah ini terasa alami. Untuk produksi, tetap pasang logging terstruktur di aplikasi dan kirim log ke tempat terpusat, jangan cuma mengandalkan journal lokal.

Environment Variables dan Secrets

Jangan tulis password atau API key langsung di unit file, apalagi kalau file-nya ikut masuk git. Simpan di file .env dalam direktori proyek:

bash
DATABASE_URL=postgresql://user:rahasia@localhost/app
SECRET_KEY=ubah-ini-jadi-string-acak

Lalu tambahkan satu baris di blok [Service] pada unit file:

bash
EnvironmentFile=/home/hermes/apps/myapp/.env

Jalankan daemon-reload dan restart. Semua variabel di file itu kini tersedia lewat os.environ di aplikasimu. Beri permission chmod 600 pada file .env supaya hanya user yang berwenang yang bisa membacanya.

Hati-hati dengan Restart=always. Kalau aplikasimu crash terus saat startup (misalnya konfigurasi salah), Restart=always membuat systemd menyalakannya tanpa henti dalam loop. Restart=on-failure juga bisa masuk loop, tapi setidaknya menghormati perintah stop manual. Pantau journalctl -f sesaat setelah deploy untuk memastikan tidak ada crash loop.

Pasang di Belakang Nginx

Service-mu sekarang hanya mendengarkan di 127.0.0.1:8000, tidak bisa diakses dari internet. Itu sengaja. Untuk melayani traffic publik, taruh Nginx sebagai reverse proxy di depannya, lengkap dengan HTTPS lewat certbot. Polanya sama untuk semua aplikasi: systemd menjaga proses tetap hidup, Nginx mengatur lalu lintas.

Selanjutnya, bungkus worker Celery atau consumer antrian jadi service dengan cara yang sama — bagian ExecStart-nya persis, hanya perintahnya yang beda. Begitu kamu menjalankan banyak service, pantau kesehatannya dengan Prometheus dan Grafana.