Bangun Vercel Sendiri di VPS? Tutorial Docker dan Coolify untuk Pemula

Bangun Vercel Sendiri di VPS? Tutorial Docker dan Coolify untuk Pemula
info Dokumentasi materi: Bangun Vercel Sendiri di VPS? Tutorial Docker dan Coolify untuk Pemula

Ringkasan. Tutorial lengkap membangun dan men-deploy proyek PHP ke VPS seperti Vercel: membuat aplikasi dari nol, mengelola Docker dan Coolify, GitHub auto-deploy, PHP CRUD dengan MariaDB, Laravel, domain HTTPS, backup–restore, rollback, monitoring, dan multi-proyek. Semua source code ditampilkan langsung dalam artikel, tanpa perlu mengunduh sample.

Bayangkan setiap proyek PHP bisa dikelola seperti aplikasi frontend modern: pilih repository, isi domain, klik Deploy, lalu perubahan berikutnya cukup git push. Itulah pengalaman yang kita bangun dengan Docker + Coolify di VPS sendiri. Pada tutorial ini kita membuat semua file langsung dari nol, sehingga pembaca tidak perlu mengunduh proyek contoh.

Ilustrasi modern alur deploy PHP otomatis seperti Vercel menggunakan Docker Coolify dan VPS
Visual pembuka: aplikasi PHP bergerak dari GitHub melalui Coolify ke container Docker pada VPS.

1. Konsep: VPS rasa Vercel

Pada hosting tradisional, pengembang sering harus login SSH, menyiapkan PHP/Apache di host, membuat virtual host, menyalin kode, lalu mengulang prosedur tersebut ketika meluncurkan proyek baru. Dalam pendekatan ini, aplikasi dipaketkan menjadi Docker image, sedangkan Coolify menyimpan konfigurasi deployment dan mengelola container, proxy, domain, environment variables, serta log.

Istilah “rasa Vercel” adalah analogi pengalaman pengembang, bukan klaim bahwa Coolify menyediakan seluruh jaminan layanan platform terkelola. Keamanan VPS, backup, kapasitas server, dan pemantauan sumber daya tetap menjadi tanggung jawab operator.

KomponenFungsiYang dikerjakan pembaca
GitHubMenyimpan kode sumber dan riwayat commitPush kode ke branch main
DockerMemastikan runtime aplikasi konsistenMenulis Dockerfile sekali, uji lokal
CoolifyMengatur build, deployment, domain, SSL, logHubungkan repository dan konfigurasi aplikasi
VPSMenjalankan container produksiSiapkan DNS, sumber daya, keamanan dan backup

2. Tools dan prasyarat

Praktik inti menggunakan aplikasi PHP tanpa framework. Pembaca membutuhkan komputer dengan Docker Desktop atau Docker Engine + Compose, Git, editor seperti VS Code, akun GitHub, VPS Linux yang didukung Coolify, serta domain yang bisa diedit DNS-nya. Contoh perintah lokal ditulis untuk Terminal macOS/Linux; Windows dapat memakai PowerShell dengan penyesuaian sintaks pembuatan folder.

ToolsKegunaanDokumentasi resmi
DockerBuild dan menjalankan containerdocs.docker.com
GitVersion control dan push repositorygit-scm.com
VS CodeMenulis source code PHPcode.visualstudio.com
GitHubHosting repository dan webhookdocs.github.com
CoolifyDeployment platform pada VPScoolify.io/docs

Periksa tools dan akses VPS

Terminal lokal
docker --version
docker compose version
git --version

Contoh output akan memuat nomor versi masing-masing tools. Apabila Docker tidak terdeteksi, jalankan atau pasang Docker terlebih dahulu. Pastikan port 80 dan 443 pada VPS terbuka untuk HTTP/HTTPS; port dashboard Coolify dibatasi sesuai kebutuhan administrasi, bukan dibuka tanpa kontrol.

3. Memahami alur GitHub → Coolify → Docker

Ilustrasi pipeline praktis: pengembang mengubah kode, GitHub mencatat commit, Coolify membangun image, Docker menjalankan container, proxy menyajikan HTTPS.
Ilustrasi pipeline praktis: pengembang mengubah kode, GitHub mencatat commit, Coolify membangun image, Docker menjalankan container, proxy menyajikan HTTPS.

Perhatikan perbedaan sumber dan runtime: GitHub menyimpan file, Coolify mengambil commit terpilih, Docker membangun image berdasarkan Dockerfile, lalu container berjalan pada server. Proxy Coolify mengarahkan domain ke port HTTP internal container. Dengan demikian, pembaca tidak perlu membuka port 8080 ke internet untuk setiap proyek.

4. Membuat struktur proyek PHP dari nol

Kita membuat aplikasi mini bernama PHP Docker Demo yang menampilkan daftar pekerjaan deployment, versi aplikasi, dan status environment. File utama berada pada folder public/ agar hanya konten yang memang boleh diakses melalui Apache yang disajikan ke web.

Terminal — buat folder
mkdir -p php-coolify-demo/public/assets
mkdir -p php-coolify-demo/tests
cd php-coolify-demo
touch public/index.php public/health.php public/assets/style.css
touch Dockerfile compose.yaml compose.coolify.yaml compose.database.yaml
touch .dockerignore .gitignore .env.example
Struktur folder yang diharapkan
php-coolify-demo/
├── public/
│   ├── index.php
│   ├── health.php
│   └── assets/
│       └── style.css
├── tests/
│   └── smoke.sh
├── Dockerfile
├── compose.yaml
├── compose.coolify.yaml
├── compose.database.yaml
├── .dockerignore
├── .gitignore
└── .env.example

Folder dan file kosong baru merupakan kerangka. Langkah berikutnya menyalin seluruh isi setiap file seperti ditampilkan di bawah. Nama file dan struktur folder harus tepat agar perintah Docker dapat menemukan berkasnya.

5. Membuat halaman PHP dan endpoint health

Langkah 1 — Tulis seluruh isi public/index.php

Buka public/index.php di VS Code, lalu ganti isinya dengan kode berikut. Kode mengambil APP_NAME dan APP_ENV dari environment, menampilkan versi, serta mencetak data task demonstrasi. Fungsi safe() mencegah karakter HTML pada data tampil sebagai markup tidak diinginkan.

public/index.php
<?php
declare(strict_types=1);

$appName = getenv('APP_NAME') ?: 'PHP Docker Demo';
$environment = getenv('APP_ENV') ?: 'production';
$version = '1.0.0';
$tasks = [
    ['name' => 'Membuat Dockerfile', 'status' => 'Selesai', 'owner' => 'Developer'],
    ['name' => 'Menghubungkan GitHub ke Coolify', 'status' => 'Selesai', 'owner' => 'DevOps'],
    ['name' => 'Mengaktifkan deploy otomatis', 'status' => 'Dalam proses', 'owner' => 'DevOps'],
];
function safe(string $value): string {
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<!doctype html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta name="description" content="Contoh aplikasi PHP Docker yang di-deploy otomatis melalui Coolify.">
  <title><?= safe($appName) ?></title>
  <link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<main class="shell">
  <div class="eyebrow">PHP + Docker + Coolify</div>
  <h1><?= safe($appName) ?></h1>
  <p>Proyek PHP sederhana yang dapat dipaketkan menjadi Docker image dan dikelola melalui Coolify.</p>
  <div class="meta"><span>Versi <?= safe($version) ?></span><span>Environment <?= safe($environment) ?></span><span>PHP <?= safe(PHP_VERSION) ?></span></div>
  <section class="panel">
    <h2>Contoh task proyek</h2>
    <div class="table-wrap"><table><thead><tr><th>Pekerjaan</th><th>Penanggung jawab</th><th>Status</th></tr></thead><tbody>
    <?php foreach ($tasks as $task): ?>
    <tr><td><?= safe($task['name']) ?></td><td><?= safe($task['owner']) ?></td><td><?= safe($task['status']) ?></td></tr>
    <?php endforeach; ?>
    </tbody></table></div>
  </section>
  <p class="note">Ubah teks ini di <code>public/index.php</code>, commit, lalu push ke branch yang terhubung ke Coolify.</p>
</main>
</body>
</html>

Langkah 2 — Buat halaman CSS

CSS berikut membuat aplikasi memiliki tampilan kartu dan tabel yang modern. CSS sengaja disimpan terpisah agar mudah dirawat dan dapat diganti tanpa mengubah logika PHP.

public/assets/style.css
:root{font-family:system-ui,-apple-system,Segoe UI,sans-serif;color-scheme:light dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;background:linear-gradient(135deg,#fef2f2,#f8fafc);color:#152238}.shell{max-width:930px;margin:8vh auto;padding:24px}.eyebrow{display:inline-block;border:1px solid #fca5a5;background:#fff1f2;color:#b91c1c;border-radius:999px;padding:8px 16px;font-weight:700}h1{font-size:clamp(2rem,5vw,3.7rem);margin:16px 0}p{line-height:1.75;color:#475569}.meta{display:flex;flex-wrap:wrap;gap:10px;margin:24px 0}.meta span{border:1px solid #e2e8f0;background:white;padding:8px 12px;border-radius:10px}.panel{margin-top:30px;background:white;border:1px solid #e2e8f0;border-radius:20px;padding:20px;box-shadow:0 16px 45px #0f172a0b}.table-wrap{overflow-x:auto}table{width:100%;border-collapse:collapse;text-align:left;min-width:480px}th,td{border-bottom:1px solid #e2e8f0;padding:14px 10px}tbody tr:last-child td{border-bottom:0}.note{font-size:.93rem}code{background:#e2e8f0;border-radius:5px;padding:3px 6px}@media(prefers-color-scheme:dark){body{background:linear-gradient(135deg,#111827,#0b1020);color:#f8fafc}p{color:#cbd5e1}.panel,.meta span{background:#192331;border-color:#334155}th,td{border-color:#334155}.eyebrow{background:#3c1f28;color:#fda4af;border-color:#9f1239}code{background:#334155}}

Langkah 3 — Buat health endpoint

Coolify atau Docker perlu cara sederhana untuk memastikan aplikasi masih merespons HTTP. Endpoint /health.php mengembalikan teks OK; untuk aplikasi yang lebih kompleks, health check dapat dibedakan menjadi liveness dan readiness.

public/health.php
<?php
declare(strict_types=1);
header('Content-Type: text/plain; charset=utf-8');
header('Cache-Control: no-store');
echo 'OK';
Hasil yang diharapkan

Halaman web menampilkan nama aplikasi, versi 1.0.0, label environment, versi PHP, dan tabel tiga task. URL /health.php mengembalikan OK.

6. Menulis Dockerfile dan berkas Docker

Ilustrasi arsitektur: PHP di dalam container Apache, terhubung ke layanan aplikasi dan proxy pada VPS.
Ilustrasi arsitektur: PHP di dalam container Apache, terhubung ke layanan aplikasi dan proxy pada VPS.

Langkah 4 — Dockerfile (Apache + PHP)

Dockerfile mendefinisikan image produksi: basis PHP+Apache, ekstensi PDO MySQL untuk latihan database berikutnya, modul Apache rewrite, direktori source, port internal 80, dan health check. EXPOSE 80 hanya mendokumentasikan port container; ia tidak membuka port host secara otomatis.

Dockerfile
FROM php:8.3-apache

RUN set -eux; \
    docker-php-ext-install pdo_mysql; \
    a2enmod rewrite headers; \
    { echo 'ServerTokens Prod'; echo 'ServerSignature Off'; } > /etc/apache2/conf-available/security-extra.conf; \
    a2enconf security-extra

COPY public/ /var/www/html/

EXPOSE 80

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
  CMD php -r 'exit(@file_get_contents("http://127.0.0.1/health.php") === "OK" ? 0 : 1);'

Langkah 5 — .dockerignore dan .gitignore

File ini mencegah file rahasia dan berkas development ikut terbawa saat proses build atau masuk commit Git. Perhatikan bahwa .env.example boleh dibagikan karena hanya berisi contoh, sedangkan .env berisi nilai privat.

.dockerignore
.git
.github
.env
.env.*
!.env.example
README.md
tests/
*.md
.gitignore
.env
.DS_Store
.idea/
.vscode/
*.log

Langkah 6 — Compose lokal dan Compose Coolify

Dua Compose dipakai untuk tujuan berbeda: file compose.yaml menerbitkan port 8080 hanya di loopback komputer lokal; file compose.coolify.yaml tidak menerbitkan port host karena akses publik akan dikelola proxy Coolify. Menggunakan file terpisah mencegah konflik port antarproyek pada VPS.

compose.yaml
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      APP_NAME: ${APP_NAME:-PHP Docker Demo}
      APP_ENV: ${APP_ENV:-production}
    ports:
      - "127.0.0.1:8080:80" # DEVELOPMENT ONLY; remove for Coolify Git Compose deployments
compose.coolify.yaml
# Repository-owned Compose for Coolify: no host port needed.
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    environment:
      APP_NAME: ${APP_NAME:-PHP Docker Demo}
      APP_ENV: ${APP_ENV:-production}
    expose:
      - "80"

7. Menjalankan aplikasi dengan Docker di komputer

Opsional — Buat tes otomatis sederhana

Sebelum pengujian lokal, isi berkas tests/smoke.sh dengan skrip berikut. Ia memeriksa endpoint kesehatan dan memastikan halaman utama mengandung label demo. Ini adalah smoke test, bukan pengganti pengujian unit atau keamanan.

tests/smoke.sh
#!/usr/bin/env sh
set -eu
URL="${1:-http://127.0.0.1:8080}"
echo "Checking ${URL}/health.php"
[ "$(curl -fsS "${URL}/health.php")" = "OK" ]
echo "Checking homepage content"
curl -fsS "${URL}/" | grep -q 'PHP + Docker + Coolify'
echo 'Smoke test passed.'

Sesudah aplikasi hidup, jalankan sh tests/smoke.sh. Hasil yang diharapkan adalah Smoke test passed. Apabila tes gagal, cek URL, status container, dan log aplikasi.

Sebelum deploy ke VPS, jalankan image yang sama di laptop. Langkah ini menangkap kesalahan PHP dan Docker lebih awal sehingga proses debugging tidak harus dimulai dari log produksi.

Terminal — build dan tes
docker compose up -d --build
docker compose ps
curl -i http://localhost:8080/
curl -i http://localhost:8080/health.php
docker compose logs --tail=50 web

Perintah pertama membangun image dan membuat container. docker compose ps menunjukkan status layanan. curl ke halaman utama seharusnya memberikan HTTP 200; ke /health.php seharusnya menghasilkan OK. Contoh output berikut adalah ekspektasi demonstrasi, bukan hasil deploy VPS pengguna.

Output contoh
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

OK

Buka http://localhost:8080 dari komputer yang menjalankan Docker. Jika port 8080 sudah dipakai, ubah hanya port sisi kiri pada 127.0.0.1:8080:80, misalnya menjadi 127.0.0.1:8081:80, lalu gunakan URL yang sesuai.

Terminal — hentikan lokal
docker compose down

8. Membuat repository GitHub dan push pertama

Buat repository kosong di GitHub, misalnya php-coolify-demo, tanpa README otomatis jika folder lokal sudah memiliki file sendiri. Setelah itu jalankan perintah berikut; ganti GITHUB_USER dengan akun Anda. Untuk akses GitHub gunakan SSH key atau Git Credential Manager, bukan menuliskan token rahasia di artikel maupun source code.

Terminal — inisialisasi Git
git init
git branch -M main
git add .
git commit -m "Initial PHP Docker app"
git remote add origin https://github.com/GITHUB_USER/php-coolify-demo.git
git push -u origin main

Pastikan file Dockerfile, compose.coolify.yaml, dan folder public terlihat pada repository GitHub, sedangkan file .env tidak ikut terkirim. Berkas proyek ditampilkan lengkap di artikel sehingga tidak ada tahap mengunduh sample project.

9. Deploy pertama di dashboard Coolify

Ilustrasi navigasi dashboard Coolify untuk membuat aplikasi dari GitHub dan mengelola deployment.
Ilustrasi navigasi dashboard Coolify untuk membuat aplikasi dari GitHub dan mengelola deployment.

Masuk ke dashboard Coolify pada VPS. Nama menu bisa sedikit berbeda menurut versi, tetapi pola pengaturannya mengikuti menu **Projects**, environment seperti **production**, dan **New Resource**. Untuk proyek tunggal berbasis Dockerfile, pilih Application yang bersumber dari GitHub; jangan memilih Service Docker Compose Empty untuk jalur dasar ini.

  1. Buka Projects, buat proyek baru misalnya php-apps, lalu buka environment production.
  2. Klik New Resource → pilih Application yang memakai sumber GitHub (GitHub App atau public repository).
  3. Hubungkan akun GitHub melalui mekanisme otorisasi resmi Coolify dan pilih repository php-coolify-demo.
  4. Pilih branch main. Pada bagian build, gunakan Dockerfile sebagai build strategy.
  5. Pastikan Base Directory mengarah ke root repository dan Dockerfile Location adalah Dockerfile.
  6. Isi Ports Exposes / port internal aplikasi dengan 80, bukan 8080.
  7. Tambahkan environment variables APP_NAME dan APP_ENV bila dibutuhkan.
  8. Klik Deploy; buka deployment log dan pastikan build serta startup container berhasil.

Coolify akan mengunduh source yang tersimpan di Git, membangun image memakai Dockerfile, menjalankan container, lalu mengatur routing domain ke HTTP internal port 80. Deployment pertama biasanya manual; setelah webhook diaktifkan, commit berikutnya dapat memicu deployment baru.

10. Memasang domain, DNS, dan HTTPS

Contoh domain tutorial: php.example.com (ganti dengan subdomain yang benar-benar Anda miliki). Buat record DNS A ke IP publik VPS dan masukkan domain yang sama ke pengaturan Domain Coolify. Sertifikat HTTPS otomatis membutuhkan DNS yang mengarah benar dan akses jaringan ke port HTTP/HTTPS yang dibutuhkan mekanisme validasi.

  1. Di pengelola DNS, buat record A untuk subdomain yang diinginkan menuju IP publik VPS.
  2. Jika menggunakan Cloudflare, periksa mode proxy dan SSL/TLS agar tidak terjadi loop HTTPS. Jangan menjadikan mode Flexible sebagai rekomendasi umum untuk origin yang juga melayani HTTPS.
  3. Pada Application → Domains, masukkan https://php.example.com atau domain sesuai antarmuka Coolify.
  4. Simpan perubahan lalu redeploy apabila diperlukan, tunggu DNS dan sertifikat berlaku, kemudian akses halaman dari browser.
  5. Periksa https://php.example.com/health.php dan pastikan hasilnya OK.
Terminal — verifikasi DNS dan HTTPS
dig +short php.example.com
curl -I https://php.example.com/
curl https://php.example.com/health.php
Checkpoint

Domain mengarah ke aplikasi PHP; browser tidak menunjukkan peringatan sertifikat dan health endpoint memberikan hasil OK. Nilai dan URL di atas merupakan contoh; sesuaikan dengan domain sebenarnya.

11. Menguji deployment otomatis dengan git push

Inilah tujuan utamanya. Setelah GitHub App/webhook dan opsi automatic deployment untuk branch main aktif, ubah satu baris versi aplikasi, commit, lalu push. Tidak perlu SSH ke VPS untuk memindahkan file aplikasi.

  1. Pada pengaturan Application, periksa integrasi GitHub serta aktifkan auto-deploy/webhook untuk branch target.
  2. Buka public/index.php pada VS Code dan ubah $version = '1.0.0'; menjadi $version = '1.1.0';.
  3. Simpan file dan jalankan perintah Git berikut.
  4. Buka halaman deployment Coolify untuk melihat build dari commit terbaru.
  5. Refresh website publik dan pastikan tulisan versi berubah menjadi 1.1.0.
Terminal — push pembaruan
git add public/index.php
git commit -m "Bump version to 1.1.0"
git push origin main

Jika versi di website tidak berubah, periksa urutannya: commit masuk ke branch benar, webhook GitHub terkirim, deployment baru dibuat, build selesai, healthcheck lolos, dan request domain mengarah ke container terbaru. Penanganan kegagalan build dan rollbacks perlu dibahas sebagai kebijakan operasional, bukan dianggap selalu otomatis sempurna.

12. VPS dari nol: keamanan dan instalasi Coolify

Coolify dapat dipasang pada VPS Linux, termasuk Ubuntu LTS. Panduan berikut menggunakan server baru dengan akses SSH dan alamat IPv4 publik. Jika Coolify sudah terpasang, Anda boleh langsung melanjutkan ke bagian GitHub App. Urutan ini penting karena dashboard Coolify mempunyai izin pengelolaan container dan server yang sensitif.

12.1 Pilih VPS dan siapkan DNS

Untuk latihan PHP–MariaDB sekaligus Coolify, siapkan VPS dengan sumber daya yang memadai (misalnya mulai 2 vCPU, 4 GB RAM, dan SSD; kebutuhan sebenarnya bergantung pada beban). Contoh domain menggunakan app.example.com dan panel.example.com; ganti dengan domain milik Anda.

KomponenContohKegunaan
VPSUbuntu 24.04 LTSMenjalankan Coolify dan Docker
A recordapp.example.com → IP VPSAlamat aplikasi publik
A recordpanel.example.com → IP VPSDashboard Coolify; amankan aksesnya
Port jaringan22, 80, 443, 8000; 6001/6002 jika dibutuhkanSSH, HTTP(S), akses dashboard awal dan terminal
Keamanan: gunakan SSH key, nonaktifkan login password setelah akses key teruji, batasi SSH dengan firewall, dan jangan membuka 3306 ke internet. Port 8000/6001/6002 hanya dibuka sementara atau sesuai kebutuhan sampai dashboard menggunakan domain HTTPS.

12.2 Hubungkan terminal melalui SSH

Di macOS/Linux gunakan Terminal, sedangkan Windows gunakan PowerShell. Ganti alamat IP dan nama pengguna. Perintah administrator perlu dilakukan pada VPS yang memang Anda miliki.

BASH · Salin kode berikut
ssh root@IP_VPS_ANDA
cat /etc/os-release
free -h
df -h

12.3 Instal Coolify menggunakan skrip resmi

Perintah instalasi berikut berasal dari dokumentasi resmi Coolify. Baca dokumentasi dan skrip terlebih dahulu sebelum mengeksekusi skrip installer dengan hak administrator.

BASH · Salin kode berikut
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash

Setelah instalasi berhasil, buka http://IP_VPS_ANDA:8000 untuk membuat administrator pertama. Segera simpan rahasia instalasi /data/coolify/source/.env secara aman di luar server. Akses dashboard sebaiknya dipindahkan ke domain HTTPS, lalu akses publik ke port dashboard langsung dibatasi.

12.4 Validasi server dan konfigurasi proxy

Di Coolify, periksa server lokal sudah terhubung, proxy berjalan, dan domain panel dapat dirutekan. Jangan mengaktifkan proxy Cloudflare secara membabi-buta pada tahap diagnostik; pastikan DNS, forwarding port, dan HTTPS pada origin benar dahulu.

BASH · Salin kode berikut
docker --version
docker ps
curl -I http://IP_VPS_ANDA:8000
Alur visual dari edit kode sampai website publik setelah push Git
Alur visual dari edit kode sampai website publik setelah push Git

13. Hubungkan GitHub App: pengaturan klik demi klik

Untuk repository privat atau workflow deployment otomatis, GitHub App paling praktis. Pembuatan GitHub App memberi Coolify izin terbatas untuk membaca repository yang Anda pilih dan menerima event perubahan. Pilih hanya repository yang diperlukan, bukan seluruh akun jika tidak dibutuhkan.

13.1 Siapkan repository

Masuk ke GitHub, buat repository baru, lalu unggah source code dengan git push. Untuk contoh CRUD pada bab berikut, root repository harus berisi Dockerfile, compose.coolify.yaml, public/, src/, dan database/.

13.2 Daftarkan GitHub App di Coolify

Buka Sources → GitHub → Add GitHub App atau menu sumber Git yang setara pada versi Coolify Anda, gunakan alur instalasi otomatis, masuk ke GitHub, kemudian pilih akun dan repository yang diizinkan. Kembali ke Coolify dan pastikan sumber menunjukkan status terhubung. Nama menu dapat berubah antarrilis.

13.3 Buat Application dari repository

Buka Projects → New Project jika proyek belum ada; pilih environment production, kemudian + New → Application → GitHub App. Pilih repository, branch main, dan build strategy Docker Compose untuk versi CRUD. Tetapkan Base Directory / dan Compose Location /compose.coolify.yaml atau jalur yang diminta UI. Periksa isi Compose setelah disimpan.

13.4 Atur variabel dan domain

Di Environment Variables, isi DB_PASSWORD dan DB_ROOT_PASSWORD dengan kata sandi berbeda yang kuat; APP_NAME, DB_NAME, dan DB_USER juga dapat ditentukan. Di Domains, pilih service web, protokol HTTPS, hostname app.example.com, internal port 80. Service db tidak diberi domain. Setelah itu pilih Deploy.

13.5 Aktifkan auto-deploy dan verifikasi

Di aplikasi, buka Advanced → Deployment → Auto deploy → Deploy on push (webhooks). Dorong commit baru pada branch yang dipilih dan amati Deployment Logs: commit yang benar, build, container, dan health check. Bila tidak muncul deployment, cek status GitHub App, webhook, branch, dan pilihan Auto deploy.

Arsitektur deployment: internet, Coolify proxy, PHP, MariaDB, dan volume Docker
Arsitektur deployment: internet, Coolify proxy, PHP, MariaDB, dan volume Docker

14. Proyek lengkap: CRUD PHP + MariaDB dari nol

Sekarang kita naik satu tingkat: bukan lagi halaman PHP statis, melainkan aplikasi penyimpanan tugas KosKu. Semua file ditampilkan di bawah sehingga pembaca tidak perlu mengunduh sample proyek. Aplikasi dapat menambah tugas, mengubah status, menghapus tugas, menyimpan database di named volume, dan menjalankan health check.

14.1 Buat folder proyek dan struktur file

Buat repository baru untuk latihan database agar mudah membedakan versi statis dan versi CRUD. Struktur ini juga cocok langsung sebagai root repository Coolify.

BASH · Salin kode berikut
mkdir -p kosku-crud/public kosku-crud/src kosku-crud/database
cd kosku-crud
touch Dockerfile compose.yaml compose.coolify.yaml .env.example .gitignore .dockerignore
touch public/index.php public/style.css public/health.php src/db.php database/init.sql
TEXT · Salin kode berikut
kosku-crud/
├── Dockerfile
├── compose.yaml                 # hanya lokal
├── compose.coolify.yaml         # deployment di Coolify
├── .env.example                 # contoh variabel, bukan rahasia
├── .gitignore
├── .dockerignore
├── public/
│   ├── index.php
│   ├── style.css
│   └── health.php
├── src/
│   └── db.php
└── database/
    └── init.sql

File: database/init.sql

SQL berikut membuat tabel tugas dengan ID, judul, status selesai, dan tanggal pembuatan. File ini dijalankan MariaDB pada inisialisasi volume yang masih kosong; file ini bukan mekanisme migrasi otomatis untuk database yang sudah berisi data.

SQL · Salin kode berikut
CREATE TABLE IF NOT EXISTS tasks (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(180) NOT NULL,
  completed TINYINT(1) NOT NULL DEFAULT 0,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

File: src/db.php

Koneksi PDO menggunakan variabel lingkungan, prepared statements, exception handling, dan charset UTF-8. Dengan demikian kredensial tidak ditulis permanen di source code.

PHP · Salin kode berikut
<?php
declare(strict_types=1);
function db(): PDO
{
    static $pdo = null;
    if ($pdo instanceof PDO) { return $pdo; }
    $host = getenv('DB_HOST') ?: 'db';
    $port = getenv('DB_PORT') ?: '3306';
    $name = getenv('DB_NAME') ?: 'kosku';
    $user = getenv('DB_USER') ?: 'kosku_user';
    $pass = getenv('DB_PASSWORD');
    if ($pass === false || $pass === '') {
        throw new RuntimeException('DB_PASSWORD belum dikonfigurasi');
    }
    $dsn = "mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4";
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]);
    return $pdo;
}

File: public/index.php

Aplikasi CRUD utama menggunakan metode POST, token CSRF sederhana, escape output HTML, validasi input, dan Post/Redirect/Get untuk mencegah submit ganda. Contoh sengaja tidak memiliki login sehingga tidak boleh dipakai untuk menyimpan data sensitif atau dibuka luas sebagai aplikasi produksi tanpa autentikasi.

PHP · Salin kode berikut
<?php
declare(strict_types=1);
ini_set('display_errors', '0');
ini_set('session.use_strict_mode', '1');
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
    ini_set('session.cookie_secure', '1');
}
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_samesite', 'Lax');
session_start();
require_once '/var/www/src/db.php';
function escape(string $text): string {
    return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
if (!isset($_SESSION['csrf'])) {
    $_SESSION['csrf'] = bin2hex(random_bytes(32));
}
$error = null;
try {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if (!hash_equals($_SESSION['csrf'], (string)($_POST['csrf'] ?? ''))) {
            http_response_code(403); exit('Permintaan ditolak (CSRF).');
        }
        $action = (string)($_POST['action'] ?? '');
        $id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
        if ($action === 'add') {
            $title = trim((string)($_POST['title'] ?? ''));
            if ($title === '' || mb_strlen($title) > 180) {
                throw new InvalidArgumentException('Judul wajib 1–180 karakter.');
            }
            $stmt = db()->prepare('INSERT INTO tasks (title) VALUES (?)');
            $stmt->execute([$title]);
        } elseif ($action === 'toggle' && $id && $id > 0) {
            $stmt = db()->prepare('UPDATE tasks SET completed=1-completed WHERE id=?');
            $stmt->execute([$id]);
        } elseif ($action === 'delete' && $id && $id > 0) {
            $stmt = db()->prepare('DELETE FROM tasks WHERE id=?');
            $stmt->execute([$id]);
        } else {
            throw new InvalidArgumentException('Aksi tidak dikenal.');
        }
        header('Location: /', true, 303); exit;
    }
    $tasks = db()->query('SELECT id,title,completed FROM tasks ORDER BY id DESC')->fetchAll();
} catch (InvalidArgumentException $e) {
    $error = $e->getMessage(); $tasks = [];
} catch (Throwable $e) {
    error_log('KosKu database error: ' . $e->getMessage());
    http_response_code(503);
    $error = 'Aplikasi sedang mengalami masalah. Coba lagi nanti.';
    $tasks = [];
}
$appName = getenv('APP_NAME') ?: 'KosKu Tasks';
?><!doctype html>
<html lang="id"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= escape($appName) ?></title><link rel="stylesheet" href="/style.css"></head>
<body><main><h1><?= escape($appName) ?></h1><p>Proyek PHP + MariaDB yang dijalankan menggunakan Docker dan Coolify.</p>
<?php if ($error): ?><div class="notice" role="alert"><?= escape($error) ?></div><?php endif; ?>
<section class="card"><h2>Tambah tugas</h2>
<form method="post" class="add"><input type="hidden" name="csrf" value="<?= escape($_SESSION['csrf']) ?>">
<input type="hidden" name="action" value="add"><input type="text" name="title" maxlength="180" placeholder="Contoh: Review desain aplikasi" required>
<button type="submit">Tambah</button></form></section>
<section class="card"><h2>Daftar tugas (<?= count($tasks) ?>)</h2><ul>
<?php foreach ($tasks as $task): ?><li><span class="<?= $task['completed'] ? 'done' : '' ?>"><?= escape($task['title']) ?></span>
<div class="actions"><form method="post"><input type="hidden" name="csrf" value="<?= escape($_SESSION['csrf']) ?>">
<input type="hidden" name="id" value="<?= (int)$task['id'] ?>"><input type="hidden" name="action" value="toggle">
<button type="submit" class="secondary">Ubah status</button></form>
<form method="post" onsubmit="return confirm('Hapus tugas ini?')"><input type="hidden" name="csrf" value="<?= escape($_SESSION['csrf']) ?>">
<input type="hidden" name="id" value="<?= (int)$task['id'] ?>"><input type="hidden" name="action" value="delete">
<button type="submit">Hapus</button></form></div></li><?php endforeach; ?>
</ul><?php if (!$tasks): ?><p>Belum ada tugas atau data belum tersedia.</p><?php endif; ?></section>
<p><small>Demo edukasi: belum memiliki autentikasi dan otorisasi akun pengguna.</small></p></main></body></html>

File: public/style.css

CSS aplikasi terpisah dari HTML Blogger; tampilannya responsif dan mengikuti preferensi dark/light pengguna.

CSS · Salin kode berikut
:root{font-family:system-ui,-apple-system,sans-serif;color-scheme:light dark}
*{box-sizing:border-box}body{margin:0;background:#f5f7fb;color:#1e293b}
main{width:min(760px,calc(100% - 32px));margin:44px auto}
h1{margin:0 0 8px;color:#b91c35}p{line-height:1.6;color:#475569}
.card{background:#fff;border:1px solid #e2e8f0;border-radius:18px;padding:24px;margin:18px 0;box-shadow:0 7px 24px #0f172a08}
form.add{display:flex;gap:9px}input[type=text]{flex:1;min-width:0;padding:12px;border:1px solid #cbd5e1;border-radius:10px}
button{background:#d7193f;color:#fff;border:0;border-radius:10px;padding:10px 14px;cursor:pointer}
button.secondary{background:#334155}ul{list-style:none;padding:0}li{display:flex;justify-content:space-between;gap:12px;align-items:center;padding:13px 0;border-bottom:1px solid #e2e8f0}
li:last-child{border:0}.actions{display:flex;gap:8px}.done{text-decoration:line-through;opacity:.6}.notice{padding:10px 12px;background:#fff1f2;border-radius:10px;color:#881337}
@media(max-width:560px){form.add,li{flex-direction:column;align-items:stretch}.actions{justify-content:flex-end}}
@media(prefers-color-scheme:dark){body{background:#0f172a;color:#e2e8f0}.card{background:#172033;border-color:#334155}p{color:#cbd5e1}h1{color:#fb7185}li{border-color:#334155}input[type=text]{background:#0b1220;color:#fff;border-color:#475569}.notice{background:#4c0519;color:#fecdd3}}

File: public/health.php

Endpoint liveness ini hanya memastikan PHP–Apache merespons; bukan menjamin MariaDB sehat. Kesehatan database diperiksa dari Compose dan tes CRUD.

PHP · Salin kode berikut
<?php
header('Content-Type: application/json; charset=utf-8');
http_response_code(200);
echo json_encode(['status' => 'ok', 'service' => 'kosku-web']);

File: Dockerfile

Image menggunakan PHP Apache, ekstensi PDO MySQL dan mbstring, curl untuk pemeriksaan kesehatan, serta menyalin source kode aplikasi.

DOCKERFILE · Salin kode berikut
FROM php:8.3-apache
RUN apt-get update && apt-get install -y --no-install-recommends curl libonig-dev \
    && docker-php-ext-install pdo_mysql mbstring \
    && a2enmod rewrite \
    && rm -rf /var/lib/apt/lists/*
COPY public/ /var/www/html/
COPY src/ /var/www/src/
RUN chown -R www-data:www-data /var/www/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
  CMD curl -fsS http://127.0.0.1/health.php || exit 1

File: .dockerignore

File rahasia dan berkas yang tidak relevan dikecualikan dari konteks build.

DOCKERIGNORE · Salin kode berikut
.git
.env
.env.*
!.env.example
backup*.sql
node_modules
vendor
README-private.md

File: .gitignore

File rahasia .env dan cadangan SQL tidak boleh di-commit ke GitHub.

GITIGNORE · Salin kode berikut
.env
.env.local
backup*.sql
.DS_Store
vendor/
node_modules/

File: .env.example

Salin ke .env untuk laptop sendiri, lalu ganti kedua password. Jangan menyalin kata sandi contoh ke produksi.

EXAMPLE · Salin kode berikut
APP_NAME=KosKu Tasks
DB_NAME=kosku
DB_USER=kosku_user
DB_PASSWORD=GANTI_DENGAN_PASSWORD_KUAT
DB_ROOT_PASSWORD=GANTI_DENGAN_PASSWORD_ROOT_BERBEDA

File: compose.yaml

File Compose lokal memetakan port 127.0.0.1:8081 ke container PHP port 80. MariaDB tidak membuka port host. Named volume membuat data tetap ada saat container dibuat ulang.

YAML · Salin kode berikut
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "127.0.0.1:8081:80"
    environment:
      APP_NAME: ${APP_NAME:-KosKu Tasks}
      DB_HOST: db
      DB_PORT: 3306
      DB_NAME: ${DB_NAME:-kosku}
      DB_USER: ${DB_USER:-kosku_user}
      DB_PASSWORD: ${DB_PASSWORD:?Isi DB_PASSWORD di .env}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
  db:
    image: mariadb:11.4
    environment:
      MARIADB_DATABASE: ${DB_NAME:-kosku}
      MARIADB_USER: ${DB_USER:-kosku_user}
      MARIADB_PASSWORD: ${DB_PASSWORD:?Isi DB_PASSWORD di .env}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?Isi DB_ROOT_PASSWORD di .env}
    volumes:
      - mariadb_data:/var/lib/mysql
      - ./database/init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 15
    restart: unless-stopped
volumes:
  mariadb_data:

File: compose.coolify.yaml

Compose Coolify tidak memakai ports: publik; domain dan proxy dipilih melalui dashboard. Database tetap privat di jaringan Compose. Kedua password wajib diisi melalui Environment Variables.

YAML · Salin kode berikut
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      APP_NAME: ${APP_NAME:-KosKu Tasks}
      DB_HOST: db
      DB_PORT: 3306
      DB_NAME: ${DB_NAME:-kosku}
      DB_USER: ${DB_USER:-kosku_user}
      DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD wajib}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
  db:
    image: mariadb:11.4
    environment:
      MARIADB_DATABASE: ${DB_NAME:-kosku}
      MARIADB_USER: ${DB_USER:-kosku_user}
      MARIADB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD wajib}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD wajib}
    volumes:
      - mariadb_data:/var/lib/mysql
      - ./database/init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 15
    restart: unless-stopped
volumes:
  mariadb_data:

15. Jalankan CRUD lokal dan uji data persisten

Sebelum deploy ke VPS, jalankan seluruh stack di komputer agar kesalahan konfigurasi cepat ditemukan. Docker Desktop diperlukan di Windows/macOS, sedangkan Linux dapat menggunakan Docker Engine dan Compose plugin.

15.1 Siapkan environment lokal

Dari folder kosku-crud, buat .env menggunakan template dan ganti kata sandinya dengan nilai kuat yang berbeda. Jangan commit file tersebut.

BASH · Salin kode berikut
cp .env.example .env
# Edit .env memakai VS Code dan ganti seluruh password contoh
docker compose -f compose.yaml config --quiet
docker compose -f compose.yaml up -d --build
docker compose -f compose.yaml ps

15.2 Uji di browser dan terminal

Kunjungi http://localhost:8081. Tambahkan tugas “Review Wireframe”, tandai selesai, tambahkan “Testing Aplikasi”, lalu hapus satu tugas. Periksa kesehatan layanan dan log bila ada masalah.

BASH · Salin kode berikut
curl -i http://localhost:8081/health.php
docker compose -f compose.yaml logs --tail=100 web
docker compose -f compose.yaml logs --tail=100 db

Hasil yang diharapkan dari health check adalah HTTP 200 dan respons JSON {"status":"ok","service":"kosku-web"}. Data tugas berasal dari MariaDB, bukan array statis.

15.3 Buktikan data tetap ada setelah container direstart

Tambahkan tugas melalui browser, hentikan stack, lalu jalankan ulang tanpa menghapus volume. Buka kembali aplikasi dan periksa tugas sebelumnya masih ada.

BASH · Salin kode berikut
docker compose -f compose.yaml down
docker compose -f compose.yaml up -d
# Buka lagi http://localhost:8081
Peringatan: docker compose down -v menghapus named volume dan dapat menghilangkan seluruh data latihan. Jangan gunakan opsi -v untuk memeriksa persistence.

15.4 Jalankan CRUD dari GitHub ke Coolify

Setelah sukses lokal, lakukan git init dan commit. Buat repository kosong lewat halaman GitHub, salin URL repository milik Anda, lalu ubah placeholder URL di bawah sebelum mengeksekusi push.

BASH · Salin kode berikut
git init
git add .
git commit -m "feat: PHP CRUD dengan MariaDB"
git branch -M main
git remote add origin https://github.com/AKUN_ANDA/kosku-crud.git
git push -u origin main

Di Coolify pilih Build strategy Docker Compose dan Compose Location /compose.coolify.yaml. Pastikan variabel MariaDB lengkap, lalu deploy. Gunakan domain HTTPS yang menunjuk ke service web port 80. Tambahkan tugas, lakukan commit perubahan CSS, tunggu redeploy, dan verifikasi data tugas masih ada.

16. Backup dan restore database: wajib dipraktikkan

Karena contoh MariaDB berada dalam stack Compose aplikasi, jangan mengasumsikan halaman backup database mandiri di Coolify otomatis mengelola database ini. Untuk database yang membutuhkan backup terjadwal dari dashboard, gunakan resource database MariaDB terpisah, atur kredensial dan jaringan, kemudian aktifkan menu Backups. Alternatif di bawah memperlihatkan dump dan restore secara manual pada komputer latihan.

16.1 Backup SQL dari Compose lokal

Perintah mengekspor database melalui program mariadb-dump di dalam container. Simpan dump ke luar server produksi serta batasi hak akses berkasnya.

BASH · Salin kode berikut
docker compose -f compose.yaml exec -T db sh -c 'mariadb-dump -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE"' > backup-kosku.sql
ls -lh backup-kosku.sql

16.2 Uji restore pada lingkungan latihan, bukan langsung produksi

Untuk latihan, buat salinan database terpisah atau gunakan lingkungan uji. Jika memang ingin mengembalikan dump ke database saat ini, pahami bahwa tabel/record bisa tertimpa dan lakukan hanya setelah memiliki backup terbaru serta memastikan kompatibilitas skema.

BASH · Salin kode berikut
cat backup-kosku.sql | docker compose -f compose.yaml exec -T db sh -c 'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE"'

16.3 Otomasi backup melalui database resource di Coolify

Untuk sistem produksi, pertimbangkan resource MariaDB terpisah yang dapat dikelola lewat Databases → Backups → Add. Tentukan jadwal (contoh harian), retensi, dan tujuan S3-compatible di luar VPS. Jalankan Back up now, lalu unduh dan uji pemulihan pada database uji. Backup sukses tanpa restore test belum cukup untuk memastikan pemulihan.

Siklus operasi: monitoring, backup, deployment, rollback, dan pemulihan data
Siklus operasi: monitoring, backup, deployment, rollback, dan pemulihan data

17. CI/CD, rollback, dan verifikasi perubahan

Deployment otomatis dan pengujian otomatis bukan hal yang sama. Webhook Coolify memulai proses build setelah push; kode yang salah secara logika bisa saja lolos build. Karena itu tambahkan minimal linting atau unit test sebelum perubahan digabungkan ke branch production.

17.1 Contoh GitHub Actions lint sederhana

Buat file .github/workflows/php-check.yml pada repository. Workflow ini memeriksa sintaks dua file PHP setiap push dan pull request. Sesuaikan daftar file untuk proyek lainnya.

YAML · Salin kode berikut
name: Check PHP Syntax
on:
  push:
  pull_request:
jobs:
  php-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
      - name: PHP syntax checks
        run: |
          find public src -name '*.php' -print0 | xargs -0 -n1 php -l
Agar hanya kode yang sudah lolos tes yang terdeploy, aktifkan perlindungan branch main dan wajibkan status check sebelum merge pull request. Jika langsung push ke main tanpa branch protection, auto-deploy dapat berjalan sebelum GitHub Actions selesai.

17.2 Memicu deployment otomatis dan membaca log

Lakukan perubahan kecil yang terlihat (misalnya teks judul atau CSS), kemudian commit dan push. Pada aplikasi Coolify buka Deployment Logs dan cocokkan commit yang diterima.

BASH · Salin kode berikut
git add public/style.css
git commit -m "style: perbarui tampilan KosKu"
git push origin main

17.3 Rollback versi aplikasi di Coolify

Buka aplikasi → Rollback, pilih image/commit terakhir yang sehat jika masih tersedia, lalu jalankan Roll back to this image. Periksa kembali URL publik, endpoint kesehatan, dan fungsi CRUD. Pilihan rollback mungkin tidak tersedia untuk seluruh jalur Compose/image retention; siapkan fallback deploy ulang dari commit Git yang diketahui baik.

Jangan disamakan: rollback hanya mengembalikan image/kode, bukan perubahan skema MariaDB, database, ataupun volume. Migrasi database harus dirancang kompatibel mundur, dan jika perlu pemulihan data, gunakan prosedur restore terpisah.

18. Laravel dengan Docker dan Coolify: jalur penerapan nyata

Untuk Laravel, struktur aplikasinya lebih kompleks daripada PHP native karena membutuhkan Composer, document root public/, APP_KEY, migration, storage, queue, dan scheduler. Tutorial ini memberi konfigurasi dasar Laravel untuk memulai; sebelum produksi, tambah autentikasi, queue dan scheduler sesuai fitur aplikasi Anda.

18.1 Membuat proyek Laravel secara lokal

Dengan Composer dan PHP versi kompatibel, buat aplikasi Laravel baru. Versi Laravel/PHP yang dipilih harus mengikuti panduan resmi yang berlaku di waktu implementasi. Gunakan instalasi Laravel resmi dan kunci dependensi di composer.lock.

BASH · Salin kode berikut
composer create-project laravel/laravel laravel-kosku
cd laravel-kosku
cp .env.example .env
php artisan key:generate
php artisan about

18.2 Contoh Dockerfile Laravel dengan Apache

Buat Dockerfile di root aplikasi. Contoh ini menyediakan PHP 8.3 dan extension umum, menginstall dependensi PHP melalui Composer stage, mengarahkan Apache DocumentRoot ke public/, dan menjalankan aplikasi sebagai image web. Untuk library lain, sesuaikan ekstensi dan versi PHP yang dibutuhkan.

DOCKERFILE · Salin kode berikut
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --prefer-dist --no-scripts --no-progress --optimize-autoloader
FROM php:8.3-apache
RUN apt-get update && apt-get install -y --no-install-recommends \
    libonig-dev libzip-dev unzip curl \
    && docker-php-ext-install pdo_mysql mbstring bcmath zip \
    && a2enmod rewrite \
    && rm -rf /var/lib/apt/lists/*
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
    /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
WORKDIR /var/www/html
COPY . .
COPY --from=vendor /app/vendor /var/www/html/vendor
RUN mkdir -p storage/framework/{cache,sessions,views} bootstrap/cache \
    && chown -R www-data:www-data storage bootstrap/cache
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -fsS http://127.0.0.1/up || exit 1
Pada proyek Laravel tertentu, Composer scripts/plugin dan asset build Vite perlu dijalankan dalam tahap build terpisah. Dockerfile dasar ini belum mengompilasi asset JS/CSS maupun menambahkan queue worker dan scheduler; tambahkan sesuai kebutuhan aplikasi. Jangan menganggap ini konfigurasi lengkap untuk semua proyek Laravel.

18.3 Contoh Compose Laravel + MariaDB

Gunakan jalur Compose berbasis Git dengan dua service. Database tetap privat; APP_KEY, DB_PASSWORD, dan DB_ROOT_PASSWORD diatur di Coolify. Untuk produksi, APP_KEY dibuat satu kali dan tidak diubah tiap deploy. Hindari menjalankan key:generate setiap kontainer dibuat ulang.

YAML · Salin kode berikut
services:
  app:
    build: .
    environment:
      APP_ENV: production
      APP_DEBUG: "false"
      APP_KEY: ${APP_KEY:?Wajib isi APP_KEY}
      APP_URL: ${APP_URL:?Wajib isi APP_URL}
      DB_CONNECTION: mysql
      DB_HOST: db
      DB_PORT: 3306
      DB_DATABASE: ${DB_NAME:-laravel_kosku}
      DB_USERNAME: ${DB_USER:-laravel_user}
      DB_PASSWORD: ${DB_PASSWORD:?Wajib isi DB_PASSWORD}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
  db:
    image: mariadb:11.4
    environment:
      MARIADB_DATABASE: ${DB_NAME:-laravel_kosku}
      MARIADB_USER: ${DB_USER:-laravel_user}
      MARIADB_PASSWORD: ${DB_PASSWORD:?Wajib isi DB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?Wajib isi DB_ROOT_PASSWORD}
    volumes:
      - laravel_db:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 15
    restart: unless-stopped
volumes:
  laravel_db:

18.4 Migrations, cache, storage, queue, dan scheduler

Setelah aplikasi dan database sehat, gunakan terminal aplikasi dalam Coolify untuk menjalankan migration terkontrol. Pada produksi, backup dulu, baca migration, dan jalankan php artisan migrate --force sebagai prosedur rilis terencana. File unggahan/storage pengguna harus memakai persistent volume atau object storage; jangan hanya menyimpannya di writable layer container.

BASH · Salin kode berikut
php artisan migrate --force
php artisan optimize
php artisan route:list
php artisan queue:work --sleep=3 --tries=3
php artisan schedule:run

Baris queue:work dan schedule:run di atas adalah contoh perintah, bukan instruksi menjalankan keduanya sekaligus di terminal web. Untuk produksi, buat service worker tersendiri dengan proses jangka panjang dan penjadwal/cron terpisah, memakai image aplikasi yang sama serta environment dan koneksi database yang sesuai. Gunakan Redis bila aplikasi memang memerlukan cache/queue Redis.

Validasi akhir Laravel: URL HTTPS berjalan, APP_DEBUG=false, APP_KEY stabil, migration sukses, file storage persisten, worker dikelola terpisah, dan backup diuji. Rujuk dokumentasi deployment Laravel.

19. Mengelola banyak proyek di satu VPS seperti PaaS

Satu instalasi Coolify dapat mengelola beberapa Application dan Service dengan domain berbeda. Agar stabil, setiap proyek sebaiknya memiliki repository, variabel, database, volume, dan alokasi resource masing-masing. Nama Project/Environment hanya membantu organisasi konfigurasi; bukan isolasi keamanan tingkat mesin virtual.

ProjectContoh domainStackData
KosKu PHPkosku.example.comPHP + MariaDBVolume mariadb_data khusus
Portal Laravelportal.example.comLaravel + DB + workerDatabase dan storage sendiri
Dashboard Reactadmin.example.comFrontend image/staticTidak perlu DB bila hanya UI
Automationflow.example.comTool berbasis DockerSecrets dan volume tersendiri

19.1 Template checklist membuat aplikasi baru

Setiap aplikasi baru mengikuti proses yang sama: buat repository, tentukan Dockerfile/Compose, set environment production, atur domain dan HTTPS, aktifkan health check, uji push otomatis, lakukan backup, tetapkan resource limit, dan dokumentasikan prosedur recovery.

19.2 Monitoring dan pemeliharaan

Gunakan Resource Metrics, Runtime Logs, Deployment Logs, health check, serta alat monitoring server. Tetapkan alarm disk, CPU, RAM, dan kapasitas volume. Update Coolify/OS secara bertahap, periksa perubahan release notes, serta uji backup sebelum pembaruan besar.

20. Troubleshooting berdasarkan gejala

Pemeriksaan efektif dimulai dari identifikasi lapisan yang gagal: GitHub webhook, build Docker, container aplikasi, koneksi database, proxy, DNS, atau HTTPS. Baca error pada lapisan tersebut sebelum mengubah konfigurasi secara acak.

GejalaPenyebab umumLangkah diagnosis
Push tidak memicu deployBranch salah, Auto deploy mati, GitHub App/webhook bermasalahPeriksa Advanced → Deployment, commit branch, GitHub App dan Deployment Logs
Deploy gagal saat buildDockerfile path salah, ekstensi/dependensi belum tersediaBaca perintah pertama yang gagal pada build log dan cocokkan repo
Halaman 502/404Service atau internal port salah, container tidak sehatCek Domains service web port 80, Healthcheck dan Runtime Logs
PHP 503 / DB refusedKredensial salah, MariaDB belum sehat, network salahCek DB_HOST=db, variabel, status db, dan log koneksi
Tugas hilang setelah redeployVolume dibuat baru atau pernah dihapusVerifikasi mounted volume, nama project dan restore cadangan
HTTPS gagalDNS salah, port 80/443 tertutup atau konfigurasi proxyCek A record, firewall, status proxy, sertifikat dan log ACME
Laravel 500APP_KEY, permission, migration, cache/configCek APP_DEBUG=false, log Laravel dan log container

21. FAQ: keputusan teknis yang sering membingungkan

Pertanyaan berikut merangkum poin yang perlu dipahami sebelum menjadikan VPS sebagai platform deployment mandiri.

Apakah semua proyek harus memakai Docker Compose?

Tidak. Satu aplikasi PHP sederhana cukup Dockerfile. Compose berguna ketika ada beberapa layanan seperti database, worker, atau Redis.

Apakah GitHub Actions wajib?

Tidak untuk auto-deploy dasar. Coolify bisa menerima webhook langsung. Actions berguna sebagai quality gate atau pipeline CI tambahan.

Apakah Coolify menggantikan DevOps?

Tidak sepenuhnya. Coolify mengotomasi banyak operasi deploy, tetapi keamanan VPS, backup, kapasitas, observability, dan recovery tetap tanggung jawab operator.

Apakah port MariaDB perlu dibuka?

Tidak. Biarkan MariaDB dapat diakses hanya dari network Docker internal. Gunakan akses terkontrol jika administrasi eksternal diperlukan.

Apakah rollback mengembalikan database?

Tidak. Rollback image/kode dan restore database adalah dua prosedur berbeda.

Kenapa memakai dua file Compose?

Lokal membutuhkan port loopback untuk uji browser. Coolify memakai proxy dan domain, sehingga aplikasi tidak perlu mempublikasikan port host.

Bagaimana kalau satu VPS kehabisan RAM?

Pantau konsumsi setiap layanan, batasi resource, optimalkan image, dan pindahkan workload/database ke server lain ketika kapasitas tidak memadai.

22. Dokumentasi dan sumber resmi

Panduan mengikuti dokumentasi resmi Coolify, Docker, GitHub, PHP, MariaDB, dan Laravel. Menu pada dashboard bisa berbeda sesuai versi yang Anda gunakan; gunakan dokumentasi berikut sebagai rujukan terakhir ketika antarmuka berubah.