Elixir untuk Technical Leader — Bab 8 dari 10
Dari laptop ke production server. Kamu harus tau cara deploy, monitor, dan rollback — karena kamu yang pegang akun Fly.io.
LabaBersih ditulis di laptop kamu pakai Elixir. Tapi server di internet gak perlu install Elixir. Yang di-upload ke server adalah binary yang sudah di-compile — kayak file .exe di Windows.
Alurnya:
# Alur deploy LabaBersih:
Code Elixir (laptop)
|
v
mix release # compile jadi binary standalone
|
v
Docker image # bungkus binary + OS minimal ke dalam "kontainer"
|
v
fly deploy # upload ke Fly.io server di Singapore
|
v
lababersih.id LIVE # Nelly bisa akses
mix release = masak makanan di dapur. Docker = bungkus ke dalam lunch box. fly deploy = kirim ke restoran (server). Customer (Nelly) tinggal makan.
Di development, kamu jalankan mix phx.server — ini butuh Elixir dan Erlang ter-install. Di production, kita gak mau install itu semua. Solusinya: Mix Release.
# Compile project jadi standalone binary
$ mix release
# Hasilnya ada di folder:
_build/prod/rel/lababersih/
# Jalankan tanpa Elixir ter-install:
$ _build/prod/rel/lababersih/bin/server
Yang terjadi saat mix release:
mix release manual. Dockerfile LabaBersih sudah otomatis menjalankannya. Tapi kamu perlu tau konsepnya — bahwa yang jalan di server bukan "code Elixir", tapi binary yang sudah di-compile.
Docker = cara standar industri untuk "bungkus" aplikasi + semua dependensinya jadi 1 paket (disebut image). Image ini bisa jalan di mana aja yang support Docker — Fly.io, AWS, Google Cloud, laptop kamu.
Phoenix otomatis generate Dockerfile saat kamu bikin project baru. LabaBersih punya Dockerfile sendiri. Ini versi simplifikasinya:
# === Stage 1: BUILD ===
# Pakai image yang sudah ada Elixir + Erlang
FROM hexpm/elixir:1.19.5-erlang-28.4.1-debian-trixie AS builder
# Install tools untuk compile
RUN apt-get update && apt-get install -y build-essential git
# Copy source code
COPY mix.exs mix.lock ./
RUN mix deps.get --only prod # download dependency
COPY lib lib
COPY config config
COPY assets assets
COPY priv priv
RUN mix compile # compile semua code
RUN mix assets.deploy # compile CSS/JS
RUN mix release # bikin binary standalone
# === Stage 2: RUN ===
# Image KECIL — cuma OS minimal, TANPA Elixir/Erlang source
FROM debian:trixie-slim AS final
# Copy HANYA binary hasil compile dari stage 1
COPY --from=builder /app/_build/prod/rel/lababersih ./
# Jalankan!
CMD ["/app/bin/server"]
Perhatikan: ada 2 stage (multi-stage build).
| Stage | Isi | Ukuran |
|---|---|---|
| Builder | Elixir, Erlang, npm, tools compile | ~1.5 GB |
| Final | Cuma OS minimal + binary hasil compile | ~120 MB |
Stage builder dibuang setelah selesai. Yang di-upload ke server cuma stage final yang kecil. Gak ada source code di server production — cuma binary.
Fly.io = hosting platform yang native untuk Phoenix/Elixir. Chris McCord (pembuat Phoenix) kerja di Fly.io. Ini bukan kebetulan — mereka optimize khusus untuk BEAM apps.
| Faktor | Fly.io | Catatan |
|---|---|---|
| Region Singapore | sin (Southeast Asia) | Terdekat ke user Indonesia. Latency rendah. |
| Phoenix-native | First-class support | fly launch otomatis detect Phoenix project |
| PostgreSQL managed | 1 command setup | Database, backup, connection pooling otomatis |
| Harga | ~$10-15/bulan | Lebih murah dari Supabase+Vercel ($45/bulan) |
| Auto-scaling | Scale to zero | Gak dipake = gak bayar (auto_stop) |
| SSL/HTTPS | Otomatis | Gak perlu setup sertifikat |
| Custom domain | Mudah | lababersih.id pointing ke Fly.io |
# Install Fly CLI (sekali aja)
$ curl -L https://fly.io/install.sh | sh
# Login ke Fly.io
$ fly auth login
# Launch project (pertama kali — auto-detect Phoenix)
$ fly launch
# ? Choose an app name: lababersih-id
# ? Choose region: sin (Singapore)
# ? Create PostgreSQL database? Yes
# Deploy!
$ fly deploy
Selesai. fly deploy melakukan semua: build Docker image, upload ke Fly.io, jalankan health check, switch traffic.
Setiap project Fly.io punya file fly.toml di root project. Ini "kontrak" antara kamu dan Fly.io — seberapa besar server, di mana, pakai port berapa.
Ini file fly.toml LabaBersih yang sesungguhnya:
# fly.toml — konfigurasi LabaBersih di Fly.io
app = 'lababersih-id' # nama app di Fly.io
primary_region = 'sin' # Singapore — terdekat ke Indonesia
[http_service]
internal_port = 4000 # port yang Phoenix dengarkan
force_https = true # selalu HTTPS (keamanan)
auto_stop_machines = 'stop' # matikan kalau gak ada traffic
auto_start_machines = true # nyalakan lagi kalau ada request
min_machines_running = 0 # boleh 0 saat idle (hemat biaya)
processes = ['app'] # nama proses
[[vm]]
memory = '1gb' # RAM 1 GB
cpus = 1 # 1 CPU
| Section | Fungsi | Yang perlu kamu tau |
|---|---|---|
app | Nama app | URL default: lababersih-id.fly.dev |
primary_region | Lokasi server | sin = Singapore. Pilihan lain: nrt (Tokyo), hkg (Hong Kong) |
[http_service] | Konfigurasi web | Port 4000 = default Phoenix. HTTPS dipaksa. |
auto_stop | Hemat biaya | Server mati otomatis kalau gak ada traffic. Nyala lagi ~2-3 detik. |
[[vm]] | Ukuran server | 1 CPU + 1 GB RAM cukup untuk 6.300 order/hari |
fly secrets (environment variables), bukan di fly.toml.
Fly.io menyediakan PostgreSQL managed — database yang sama dengan yang dipakai di development. Setup-nya 1 command:
# Buat database (pertama kali, sudah dilakukan)
$ fly postgres create --name lababersih-db --region sin
# Attach ke app (otomatis set DATABASE_URL)
$ fly postgres attach lababersih-db
Setelah attach, Fly.io otomatis set environment variable DATABASE_URL di app kamu. Phoenix baca ini di config/runtime.exs.
| Fitur | Status | Catatan |
|---|---|---|
| Automatic backups | Ada | Point-in-time recovery. Bisa restore ke menit tertentu. |
| Connection pooling | Built-in | PgBouncer otomatis. Pool size 10 (default LabaBersih). |
| Region co-location | Otomatis | DB di Singapore, app di Singapore. Latency minimal. |
| Storage | 10 GB (default) | Bisa naikkan. LabaBersih ~5 juta operasi/bulan cukup. |
| Monitoring | Dashboard | Query stats, connection count, storage usage. |
# Buka psql console ke database production
$ fly postgres connect -a lababersih-db
# Cek jumlah order
lababersih=# SELECT count(*) FROM orders;
# Cek ukuran database
lababersih=# SELECT pg_size_pretty(pg_database_size('lababersih'));
fly postgres connect = akses langsung ke database production. JANGAN jalankan DELETE atau DROP tanpa yakin. Query SELECT aman.
Informasi sensitif (password, API key, secret) gak boleh ditulis di code. Mereka disimpan sebagai environment variables di Fly.io.
| Variable | Fungsi | Cara generate |
|---|---|---|
SECRET_KEY_BASE | Enkripsi cookies & session Phoenix | mix phx.gen.secret |
DATABASE_URL | Koneksi ke PostgreSQL | Otomatis saat fly postgres attach |
GUARDIAN_SECRET_KEY | Sign JWT token untuk auth | mix phx.gen.secret |
# Set 1 secret
$ fly secrets set SECRET_KEY_BASE=abc123supersecretlongstring
# Set beberapa sekaligus
$ fly secrets set \
GUARDIAN_SECRET_KEY=anotherlongstring \
PHX_HOST=lababersih.id
# Lihat daftar secrets (value GAKK ditampilkan, cuma nama)
$ fly secrets list
NAME DIGEST CREATED AT
DATABASE_URL abc123... 2026-03-30
GUARDIAN_SECRET_KEY def456... 2026-03-30
SECRET_KEY_BASE ghi789... 2026-03-30
# Hapus secret
$ fly secrets unset NAMA_SECRET
Di file config/runtime.exs. Ini file yang dibaca saat app start (bukan saat compile):
# config/runtime.exs — dibaca saat app start di production
if config_env() == :prod do
# Ambil DATABASE_URL dari environment
database_url =
System.get_env("DATABASE_URL") ||
raise "DATABASE_URL is missing"
config :lababersih, Lababersih.Repo,
url: database_url,
pool_size: 10
# Ambil SECRET_KEY_BASE dari environment
secret_key_base =
System.get_env("SECRET_KEY_BASE") ||
raise "SECRET_KEY_BASE is missing"
config :lababersih, LababersihWeb.Endpoint,
secret_key_base: secret_key_base
# Ambil GUARDIAN_SECRET_KEY dari environment
guardian_secret =
System.get_env("GUARDIAN_SECRET_KEY") ||
raise "GUARDIAN_SECRET_KEY is missing"
config :lababersih, Lababersih.Auth.Guardian,
secret_key: guardian_secret
end
Perhatikan pattern System.get_env("...") || raise "..." — kalau secret gak ada, app gak mau start dan kasih pesan jelas. Lebih baik gagal keras daripada jalan dengan config salah.
| Variable | Kapan | Fungsi |
|---|---|---|
RESEND_API_KEY | Part E | Kirim email (reset password, invite) |
SENTRY_DSN | Part H | Error tracking |
TIKTOK_SHOP_APP_KEY | Part G | TikTok Shop API |
TIKTOK_SHOP_APP_SECRET | Part G | TikTok Shop API |
MENGANTAR_API_KEY | Part G | Mengantar API |
Setiap kali Claude selesai bikin fitur dan kamu mau push ke production:
# 1. Pastikan tests pass
$ mix test
268 tests, 0 failures
# 2. Commit perubahan
$ git add .
$ git commit -m "feat: laporan keuangan UI"
# 3. Deploy ke Fly.io
$ fly deploy
fly deploy:# Proses fly deploy (otomatis):
1. Upload source code ke Fly.io builder
|
2. Build Docker image (sesuai Dockerfile)
├── Install dependencies (mix deps.get)
├── Compile code (mix compile)
├── Build assets (mix assets.deploy)
└── Create release (mix release)
|
3. Push image ke Fly.io registry
|
4. Start mesin baru dengan image baru
|
5. Health check — cek app respond HTTP 200
|
6. Switch traffic ke mesin baru
|
7. Matikan mesin lama
|
Deploy complete!
Total waktu: 3-5 menit. Selama deploy, app lama tetap jalan — zero downtime.
Fly.io cek apakah app baru sudah ready sebelum switch traffic. Kalau health check gagal (app crash, gak respond), Fly.io gak switch — app lama tetap jalan. Deploy ditandai "failed" tapi user gak terkena dampak.
Kalau ada migration baru (tabel baru, kolom baru), migration dijalankan otomatis saat app start — sebelum menerima traffic. Ini diatur di file rel/overlays/bin/migrate.
Sebagai leader, kamu perlu tau cara cek: apakah app jalan? Ada error? Performance oke?
Buka fly.io/apps/lababersih-id di browser:
fly status (Terminal)$ fly status
App
Name = lababersih-id
Status = deployed
Version = 42
Machines
ID STATE REGION CREATED
e784e2 started sin 2026-03-30T15:01:20Z
Ini cara tercepat cek: apakah app jalan?
fly logs (Real-time)# Lihat log production real-time
$ fly logs
# Output contoh:
2026-04-02 10:30:15 [info] GET /app/order
2026-04-02 10:30:15 [info] Sent 200 in 45ms
2026-04-02 10:30:18 [info] GET /app/produk
2026-04-02 10:30:18 [info] Sent 200 in 32ms
2026-04-02 10:31:01 [error] ** (Ecto.NoResultsError) no results for Order query
2026-04-02 10:31:01 [error] Lababersih.Orders.get_order!/1
Ini alat utama debugging. Kalau ada user lapor error, buka fly logs dan cari pesan errornya.
Phoenix punya monitoring dashboard built-in di /dev/dashboard (hanya di development). Di production, bisa diaktifkan tapi butuh auth khusus.
Yang bisa dilihat:
| Tab | Isi | Berguna kapan |
|---|---|---|
| Home | Memory usage, atom count, process count | Cek apakah ada memory leak |
| OS | CPU usage, memory, disk | Server kepenuhi gak? |
| Ecto Stats | Query paling lambat, query paling sering | Optimasi database |
| Processes | Semua BEAM process yang jalan | Debugging (advanced) |
| Oban | Background jobs status | Cek daily journal batch jalan gak |
Sentry = layanan tracking error otomatis. Setiap kali ada error di production, Sentry kirim notifikasi (email/Slack) dengan detail lengkap: error apa, user mana, stack trace.
# Nanti di mix.exs:
{:sentry, "~> 10.0"}
# Nanti di config/prod.exs:
config :sentry,
dsn: System.get_env("SENTRY_DSN"),
environment_name: :prod
fly statusfly logsfly ssh consoleIni fitur paling keren — kamu bisa buka IEx (Elixir console) langsung di server production:
# SSH ke server production
$ fly ssh console
# Jalankan IEx (Elixir interactive shell)
$ /app/bin/lababersih remote
# Sekarang kamu di IEx production! Bisa query langsung:
iex> Lababersih.Repo.aggregate(Lababersih.Orders.Order, :count)
4643
iex> Lababersih.Accounts.get_user_by_email("nelly@lababersih.com")
%User{name: "Nelly", ...}
| Masalah | Gejala | Solusi |
|---|---|---|
| Deploy gagal | Error saat fly deploy |
Baca error message. Biasanya: compile error (fix code), health check timeout (app gak start). Cek fly logs. |
| App crash loop | App start lalu mati terus | fly logs — cari error. Biasanya: secret missing, database gak connect, migration gagal. |
| Database connection refused | "could not connect to server" | Cek fly postgres list. Restart DB: fly machine restart -a lababersih-db |
| Migration gagal | App start tapi data gak ada | fly ssh console → /app/bin/migrate untuk run manual |
| Out of memory | App lambat lalu mati | Naikkan RAM di fly.toml: memory = '2gb', lalu fly deploy |
| Secret missing | "SECRET_KEY_BASE is missing" di log | fly secrets set SECRET_KEY_BASE=... |
| HTTPS error / mixed content | Browser warning "not secure" | Pastikan force_https = true di fly.toml. Cek DNS pointing. |
| Slow response | TTFB > 300ms | Cek LiveDashboard Ecto Stats. Missing index? N+1 query? Cek fly logs response time. |
Deploy baru ternyata ada bug? Rollback ke versi sebelumnya dalam hitungan detik.
# Lihat daftar semua release (versi deploy)
$ fly releases
VERSION STATUS CREATED
v42 current 2026-04-02T10:30:00Z
v41 - 2026-04-01T15:00:00Z
v40 - 2026-03-31T12:00:00Z
# Rollback ke versi sebelumnya
$ fly deploy --image registry.fly.io/lababersih-id:deployment-v41
# Atau cara cepat: redeploy dari git commit sebelumnya
$ git checkout v41-commit-hash
$ fly deploy
fly logsfly releases — cari versi yang terakhir stabilfly deploy --image ... — rollback ke versi ituLabaBersih v1 di Supabase, v2 di Fly.io. Keduanya pakai PostgreSQL — jadi data bisa dipindahkan langsung.
# Export data dari Supabase (v1)
$ pg_dump --data-only --no-owner -F c \
"postgresql://user:pass@supabase-host/db" \
-f v1_backup.dump
# Import data ke Fly.io (v2)
$ pg_restore --no-owner --no-privileges \
-d "postgresql://user:pass@fly-host/db" \
v1_backup.dump
Fly.io bukan satu-satunya pilihan. Kalau suatu hari mau pindah (zero vendor lock-in karena Docker), ini perbandingannya:
| Provider | Elixir Support | Harga/bulan | Region Asia | Pro | Kontra |
|---|---|---|---|---|---|
| Fly.io | Native (Phoenix-first) | $10-15 | Singapore | Terbaik untuk Phoenix. Scale to zero. Chris McCord di sini. | Docs kadang kurang. UI dashboard basic. |
| Gigalixir | Native (Elixir-only) | $10+ (free tier ada) | US & EU only | Dibuat khusus Elixir. Hot upgrade support. | Gak ada region Asia. Latency tinggi dari Indonesia. |
| Render | Docker | $7+ | Singapore | UI bagus. Simple. PostgreSQL managed. | Gak ada BEAM-specific feature. Cold start lebih lama. |
| Railway | Docker | $5+ | US only | Deploy dari GitHub otomatis. Developer-friendly. | Gak ada region Asia. Pricing bisa unpredictable. |
| AWS (EC2/ECS) | Docker/manual | $15-50+ | Singapore, Jakarta | Enterprise-grade. Region Jakarta. Scalable. | Kompleks banget. Butuh DevOps knowledge. Overkill untuk skala ini. |
| Google Cloud Run | Docker | $5-20 | Jakarta | Pay per request. Region Jakarta ada. | WebSocket (LiveView) butuh config khusus. Stateless-focused. |
| DigitalOcean | Docker/manual | $6-12 | Singapore | Simple. Murah. App Platform support Docker. | Gak ada BEAM-specific. PostgreSQL managed tambah biaya. |
| VPS (manual) | Install sendiri | $5-10 | Banyak pilihan | Paling murah. Full kontrol. | Semua manual: SSL, backup, monitoring, update OS. Risky tanpa DevOps. |
Setiap fitur LabaBersih punya target response time. Ini bukan ngasal — berdasarkan standar UX research bahwa user mulai merasa "lambat" di atas 300ms.
| Tipe Halaman | Target | Contoh | Cara cek |
|---|---|---|---|
| List page | < 200ms | /app/order, /app/produk | Lihat "Sent 200 in Xms" di fly logs |
| Detail page | < 100ms | /app/order/:id | Sama — di logs |
| Create/Update | < 300ms | Buat order, update produk | Sama |
| Heavy operation | < 500ms | Ship order (5 step atomic) | Sama + LiveDashboard Ecto Stats |
| TTFB (general) | < 300ms | Semua halaman | Browser DevTools → Network tab |
# 1. Cek response time di logs
$ fly logs | grep "Sent"
# Sent 200 in 450ms ← INI LAMBAT, target < 200ms
# 2. Buka LiveDashboard, cek Ecto Stats
# Cari query yang > 100ms → kemungkinan missing index
# 3. Pastikan query pakai index
# Di IEx remote:
iex> Ecto.Adapters.SQL.explain(Lababersih.Repo, :all,
from o in Order, where: o.status == "dikirim")
# Cari: "Seq Scan" = LAMBAT (gak pakai index)
# Harus: "Index Scan" = CEPAT
| Item | v1 (Supabase + Vercel) | v2 (Fly.io) |
|---|---|---|
| Hosting (app) | $20 (Vercel Pro) | ~$5-8 (Fly.io VM) |
| Database | $25 (Supabase Pro) | ~$5-7 (Fly.io Postgres) |
| SSL | Included | Included |
| Domain | Terpisah | Terpisah (~$10/tahun) |
| Total | $45/bulan | ~$10-15/bulan |
| Hemat ~$30/bulan ($360/tahun) dengan infrastruktur yang lebih baik | ||
Sebelum setiap deploy, pastikan:
mix test harus 0 failuresmix compile --warnings-as-errors harus tanpa warningfly secrets list cek semua adagit status cleanfly statusfly logs tunggu 1-2 menit| Mau apa? | Command |
|---|---|
| Cek app jalan | fly status |
| Lihat error | fly logs |
| Deploy | fly deploy |
| Rollback | fly releases lalu fly deploy --image ... |
| Set secret | fly secrets set KEY=value |
| List secrets | fly secrets list |
| SSH ke server | fly ssh console |
| IEx di production | fly ssh console → /app/bin/lababersih remote |
| Akses DB | fly postgres connect -a lababersih-db |
| Restart app | fly machine restart |
| Scale up | Edit fly.toml → fly deploy |
| Cek biaya | fly billing atau dashboard web |
Klaim: "LabaBersih bisa pindah hosting kapan aja tanpa rewrite." Ini buktinya:
| Layer | Teknologi | Portable? |
|---|---|---|
| Code | Elixir/Phoenix | Ya — bahasa + framework, gak terikat hosting |
| Database | PostgreSQL | Ya — standar industri, jalan di mana aja. pg_dump/pg_restore. |
| Container | Docker | Ya — image yang sama bisa jalan di AWS, GCP, DigitalOcean, dll. |
| Config | Environment variables | Ya — standar 12-factor app. Semua hosting support env vars. |
| Background jobs | Oban (PostgreSQL-based) | Ya — pakai database yang sama, gak butuh Redis/external queue. |
| Fly.io-specific | Hanya fly.toml |
1 file config, mudah di-replace dengan config hosting lain. |
fly.toml (20 baris). Semua sisanya — code, database, Docker, secrets — standar industri. Mau pindah ke AWS besok? Copy Docker image, set env vars, selesai.
| Konsep | Satu Kalimat |
|---|---|
| Mix Release | Compile Elixir jadi standalone binary — server gak perlu install Elixir. |
| Dockerfile | Resep kontainer: 2-stage build, image final cuma ~120 MB. |
| Fly.io | Hosting Phoenix-native di Singapore, $10-15/bulan, 1 command deploy. |
| fly.toml | Config file 20 baris: nama app, region, RAM, port. |
| Database | PostgreSQL managed di Fly.io: backup otomatis, connection pooling. |
| Env vars | Secrets (password, API key) disimpan terenkripsi di Fly.io, bukan di code. |
| Deploy | fly deploy = build Docker + health check + switch traffic. Zero downtime. |
| Monitor | fly status (up?), fly logs (errors?), LiveDashboard (performance). |
| Troubleshoot | fly ssh console + /app/bin/lababersih remote = IEx di production. |
| Rollback | fly releases → fly deploy --image previous. Detik, bukan jam. |
| Data migration | pg_dump + pg_restore. Same PostgreSQL, same bcrypt. Password compatible. |
| Lock-in | Zero. Docker + PostgreSQL + env vars = standar industri. |
| Performance | List < 200ms, detail < 100ms, create < 300ms, TTFB < 300ms. |
fly status), cara lihat error (fly logs), cara rollback (fly deploy --image), dan berapa biayanya ($10-15/bulan). Claude yang handle sisanya — tapi kamu yang pegang akun.