# Cara Deploy Next.js ke VPS dengan Docker dan GitHub Actions

> Cara deploy Next.js ke VPS untuk pemula: Docker standalone, GHCR, dan GitHub Actions lewat SSH. Ada perintah siap salin, output, dan tabel error umumnya.

URL: https://www.matthewswong.com/id/blog/cara-deploy-nextjs-ke-vps-docker-github-actions/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-10-17
- Updated: 2026-10-17
- Category: DevOps

> **Key takeaway:** Cara deploy Next.js ke VPS yang rapi: aktifkan output standalone, bungkus aplikasi dalam Docker image, push image ke GHCR lewat GitHub Actions, lalu workflow masuk ke VPS dengan SSH dan menjalankan docker compose up -d --wait. Setiap push ke main otomatis menjadi deploy, dan rollback cukup mengganti tag image.

Kamu sudah punya aplikasi Next.js yang jalan di laptop dan sebuah VPS kosong. Lalu macet: apakah harus npm run build di server? Salin folder lewat FTP? Instal Node di server? Hampir semua tutorial berhenti di langkah ssh lalu git pull, padahal masalah sebenarnya muncul sesudahnya: build memakan RAM server, versi Node berbeda dengan laptop, dan tiap update harus diulang manual.

Panduan ini menunjukkan urutan kerja yang lebih aman untuk pemula: build sekali di GitHub Actions, kirim hasilnya sebagai Docker image, dan biarkan VPS cukup menarik image itu. Urutannya sama dengan yang dipakai situs ini sendiri, lengkap dengan Dockerfile dan workflow di repo-nya. Semua alamat dan nama di bawah adalah contoh: ganti dengan milikmu.

## Mau belajar konsepnya, bukan hanya langkahnya?

Deploy Docker ke VPS dengan GitHub Actions dan GHCR

menjelaskan kenapa job dirangkai dengan needs, kapan build boleh dibatalkan, dan bagaimana secret aman.

Self-Host Next.js: Output Standalone, Docker, dan Env Var

menjelaskan isi image runner, env var saat build vs runtime, dan health check.

Yang kamu butuhkan

Siapkan lima hal ini sebelum mulai. Tanpa salah satunya, langkah di bawah akan berhenti di tengah jalan.

Project Next.js (App Router) yang sudah di-push ke repository GitHub, dengan branch utama bernama main.

Docker terpasang di laptop untuk mencoba image, dan di VPS (ikuti panduan instalasi resmi Docker untuk distro-mu).

VPS Linux dengan akses SSH sebagai user yang punya sudo. Di contoh ini alamatnya 203.0.113.10, alamat contoh yang memang disediakan untuk dokumentasi.

Script npm run lint dan npm run build di package.json, karena workflow di langkah 6 memanggilnya.

Reverse proxy seperti nginx di VPS untuk membuka aplikasi ke internet. Panduan ini berhenti di container yang sehat di port lokal.

Langkah 1: Aktifkan output standalone dan coba di laptop

Standalone membuat next build menulis folder .next/standalone berisi server.js minimal dan hanya file node_modules yang benar-benar dibutuhkan, jadi folder itu bisa dijalankan tanpa npm install. Ini yang membuat image kecil dan cepat dikirim. Tambahkan satu baris di konfigurasi.

Sebelum membungkusnya dengan Docker, jalankan dulu di laptop. Dokumentasi Next.js menyebut server minimal ini tidak menyalin folder public dan .next/static secara default, jadi kamu menyalinnya sendiri. Perhatikan bedanya antara cara yang salah dan yang benar.

Yang akan kamu lihat: server berjalan dan mencetak alamat tempat ia mendengarkan. Pada versi yang salah, halaman HTML muncul tetapi tanpa gaya, dan panel Network di browser dipenuhi 404 untuk file di /_next/static. Pada versi yang benar, halamannya tampil utuh. Kalau sudah begitu, lanjut ke Docker.

Langkah 2: Tulis Dockerfile multi-stage

Dockerfile ini punya tiga stage: deps memasang dependency, builder menjalankan npm run build, dan runner hanya membawa hasil standalone. Ini versi yang disederhanakan dari Dockerfile situs ini. Sesuaikan versi Node dengan yang kamu pakai di project.

Tambahkan juga .dockerignore supaya node_modules dari laptop dan file .env berisi secret tidak ikut masuk ke image. Lalu coba bangun dan jalankan image-nya di laptop.

Yang akan kamu lihat: docker build selesai tanpa error, lalu curl -I mengembalikan baris status HTTP dari server Next.js. Kalau curl gagal tersambung, periksa dulu apakah ENV HOSTNAME dan flag -p 3000:3000 sudah ada.

Ingin image-nya lebih ramping lagi? Baca

ukuran image Docker standalone Next.js

 setelah deploy pertamamu berhasil.

Langkah 3: Siapkan VPS, user deploy, dan SSH key

GitHub Actions perlu masuk ke VPS tanpa password, jadi buat key pair khusus untuk pipeline. Jangan pakai key pribadimu, supaya kalau suatu hari bocor kamu cukup mencabut satu key. Perintah ssh-keygen dan ssh-copy-id di bawah memakai -t ed25519, -C untuk komentar, dan -f untuk nama file.

Yang akan kamu lihat: perintah terakhir langsung masuk tanpa meminta password, lalu docker ps mencetak tabel container, kosong kalau belum ada. Kalau muncul Permission denied (publickey), key belum terpasang di user deploy; ulangi ssh-copy-id.

Menambahkan user ke grup docker sama dengan memberinya akses setara root. Dokumentasi Docker menulis bahwa grup docker memberi hak istimewa tingkat root. Itu tradeoff yang wajar untuk user deploy, tetapi jangan berikan ke user lain, dan jangan pakai key ini untuk hal selain deploy.

Langkah 4: Tulis docker-compose.yml untuk server

Compose file ini yang menjalankan container di VPS. Perhatikan bahwa nama image tidak ditulis mati: ia dibaca dari variabel IMAGE di file .env yang akan ditulis pipeline di tiap deploy. Itu yang membuat satu commit menunjuk tepat satu image.

Situs ini memakai pola yang sama: port hanya dibuka di 127.0.0.1 dan nginx di depannya yang menerima trafik dari internet. Health check meminta satu route murah dari dalam container, dan start_period memberi server waktu untuk menyala sebelum kegagalan dihitung. Taruh file ini di repo, di root, supaya workflow bisa menyalinnya ke server.

Langkah 5: Isi secret dan variable di GitHub

Workflow membaca alamat server dan key dari GitHub, bukan dari file di repo. Menurut dokumentasi GitHub, jalurnya seperti ini.

1. Buka repository di GitHub, lalu klik Settings.
2. Di bagian Security pada sidebar, klik Secrets and variables, lalu Actions.
3. Di tab Secrets klik New repository secret, isi Name dan Secret, lalu Add secret. Ulangi untuk tiga secret di tabel.
4. Pindah ke tab Variables dan buat satu variable, DEPLOY_PATH. Ia bukan rahasia, jadi cukup berupa variable.

Nama

Isi

Jenis

Alamat IP atau hostname VPS, misalnya 203.0.113.10

Secret

Nama user di VPS, yaitu deploy

Secret

Isi lengkap file deploy_key (private key), termasuk baris BEGIN dan END. Jangan tempel file .pub.

Secret

Folder di server tempat compose file berada, misalnya /home/deploy/app

Variable

Setelah key tersimpan di GitHub, hapus file deploy_key dari laptop atau simpan di password manager. Key privat tidak boleh masuk ke repo, ke chat, atau ke screenshot.

Langkah 6: Tulis workflow GitHub Actions

Buat file .github/workflows/deploy.yml. Workflow ini punya tiga job yang dirangkai: verify memeriksa commit, build-and-push membuat image dan mendorongnya ke GHCR, lalu deploy masuk ke VPS. Ini versi ringkas dari deploy.yml situs ini.

Tiga hal di sini sering membuat pemula tersandung. Pertama, needs membuat commit yang gagal verify tidak pernah sampai ke server. Kedua, image diberi tag SHA commit, jadi server menjalankan build yang persis lolos tadi, bukan latest yang bisa berpindah. Ketiga, token GITHUB_TOKEN dibuat GitHub untuk tiap job, jadi tidak ada password registry yang perlu kamu simpan. Menurut dokumentasi GitHub, package yang baru dipublikasikan berstatus private secara default, itu sebabnya server perlu docker login sebelum pull.

Situs ini sendiri memakai docker compose up -d tanpa --wait, jadi job bisa hijau padahal health check belum lolos. Menurut dokumentasi Docker, --wait menunggu service berstatus running atau healthy dan otomatis berarti detached mode. Saya menaruhnya di versi di atas karena itu yang saya anggap lebih aman untuk pemula.

Build terasa lambat? Strategi cache di

Docker build cache untuk deploy yang lebih cepat

 bisa dipasang setelah pipeline dasar ini jalan.

Langkah 7: Push, pantau, dan siapkan rollback

Commit semua file lalu push ke main. Buka tab Actions di GitHub dan tunggu ketiga job hijau. Setelah itu cek dari sisi server.

Yang akan kamu lihat: STATUS container berubah dari starting menjadi healthy setelah start_period lewat, dan curl mengembalikan respons HTTP dari aplikasimu. Kalau deploy baru bermasalah, kamu tidak perlu membangun ulang apa pun: karena setiap image punya tag SHA, rollback hanya berarti menunjuk IMAGE ke tag lama.

Container dibuat ulang saat deploy, jadi ada jeda singkat. Kalau jeda itu tidak bisa diterima, pelajari

zero-downtime rolling deploy

 sebagai langkah berikutnya.

Error umum dan cara memperbaikinya

Ini yang paling sering terjadi saat pertama kali. Cocokkan gejala yang kamu lihat di log dengan kolom pertama.

Gejala

Penyebab

Perbaikan

Permission denied (publickey) saat deploy atau saat ssh manual

Public key belum ada di authorized_keys user deploy, atau secret VPS_SSH_KEY berisi file yang salah.

Ulangi ssh-copy-id, tes dengan ssh -i, lalu tempel ulang private key lengkap (BEGIN sampai END) ke secret.

invalid reference format, repository name must be lowercase

Nama owner atau repository di GitHub memakai huruf besar, sedangkan nama image di registry wajib huruf kecil.

Ubah ke huruf kecil dengan tr seperti di step Compute lowercase image ref, jangan ketik nama image manual.

pull access denied atau unauthorized saat docker compose pull

Package di GHCR berstatus private dan server belum login ke ghcr.io.

Pastikan baris docker login ghcr.io dengan --password-stdin berjalan sebelum pull, dan job deploy punya permission packages: read.

permission denied untuk docker.sock di server

User deploy belum masuk grup docker, atau sesi belum dimuat ulang.

Jalankan sudo usermod -aG docker deploy, lalu keluar dan masuk lagi (atau newgrp docker).

Halaman tampil tanpa gaya dan banyak 404 di /_next/static

Folder public dan .next/static tidak disalin ke image runner.

Tambahkan dua baris COPY ke stage runner di Dockerfile, lalu build ulang image.

Container jalan di server tetapi tidak bisa dibuka dari internet

Port sengaja dibuka di 127.0.0.1 saja, dan belum ada reverse proxy yang meneruskan ke port itu.

Pasang nginx yang proxy ke 127.0.0.1:3000. Jangan ganti ke 3000:3000 hanya supaya cepat terbuka, karena itu melewati proxy.

Job deploy hijau, tetapi situs masih versi lama atau error

docker compose up -d selesai begitu container menyala, sebelum health check lolos, sehingga rilis rusak tetap terlihat sukses.

Pakai up -d --wait supaya job gagal kalau container tidak healthy, lalu lihat docker compose logs web.

no space left on device saat build atau pull

Disk penuh karena setiap deploy menarik image baru dengan tag SHA yang berbeda dan yang lama menumpuk.

Jalankan docker image prune -af --filter until=72h di server. Situs ini menaruh perintah itu di akhir script deploy.

Checklist sebelum push pertama

next.config.ts memakai output standalone, dan node .next/standalone/server.js jalan di laptop.

Dockerfile menyalin public dan .next/static, dan docker run menjawab di port 3000.

ssh -i deploy_key deploy@alamat-vps masuk tanpa password.

Tiga secret dan satu variable sudah terisi, dan file deploy_key sudah dihapus dari laptop.

Port di compose file terikat ke 127.0.0.1, dan reverse proxy sudah siap.

Kamu tahu cara rollback: ganti IMAGE di .env ke tag SHA lama lalu docker compose up -d --wait.

Belajar lebih dalam

Panduan ini menjawab bagaimana urutannya. Kalau kamu ingin paham kenapa tiap bagian dibuat begitu, dua pelajaran gratis ini membahasnya dengan diagram interaktif dan kuis singkat.

Pelajaran: deploy Docker ke VPS dengan GitHub Actions dan GHCR

Pelajaran: self-host Next.js dengan output standalone dan Docker

Aturan yang bisa kamu bawa: build di CI, kirim image, dan biarkan server hanya menarik dan menjalankan. Satu commit menjadi satu image bertag SHA, dan itu membuat deploy bisa diulang serta di-rollback.

## Sumber dan referensi

- Dokumentasi Next.js: output (standalone dan file tracing)
- Dokumentasi Docker: docker compose up (--wait, --remove-orphans)
- Dokumentasi GitHub: bekerja dengan Container registry
- Dokumentasi GitHub: memakai secret di GitHub Actions
- Dokumentasi Docker: langkah pasca-instalasi Linux (grup docker)
- README appleboy/ssh-action (input yang tersedia)

## Source links

- https://nextjs.org/docs/app/api-reference/config/next-config-js/output
- https://docs.docker.com/reference/cli/docker/compose/up/
- https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry
- https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets
- https://docs.docker.com/engine/install/linux-postinstall/
- https://github.com/appleboy/ssh-action

## FAQ

### Kenapa container Docker Next.js jalan tetapi halamannya tanpa CSS dan gambar?

Build standalone tidak menyertakan folder public dan .next/static secara default, jadi server.js tidak punya file untuk dilayani. Tambahkan dua baris COPY untuk folder itu di stage runner pada Dockerfile lalu build ulang image. Setelah itu minta satu file di /_next/static langsung untuk memastikan tidak lagi 404.

### Apakah saya perlu menginstal Node.js di VPS untuk deploy Next.js dengan Docker?

Tidak perlu. Runtime Node.js sudah ada di dalam Docker image, jadi server hanya butuh Docker dan Docker Compose. Itu juga menghilangkan masalah versi Node yang berbeda antara laptop dan server.

### Kenapa GitHub Actions error Permission denied (publickey) saat deploy lewat SSH?

Biasanya public key pasangannya belum ada di authorized_keys user deploy, atau secret berisi public key atau private key yang terpotong. Tes dulu key yang sama dari laptop dengan ssh -i. Lalu tempel ulang private key lengkap, dari baris BEGIN sampai END, ke secret repository.

### Apakah aman menambahkan user deploy ke grup docker?

Itu sebuah tradeoff, karena dokumentasi Docker menyebut grup docker memberi hak istimewa tingkat root. Untuk user deploy khusus hal ini lazim, selama key-nya terpisah dan hanya dipegang pipeline. Jangan masukkan user login harian ke grup itu.

### Bagaimana cara rollback deploy Next.js dengan GitHub Actions dan Docker?

Setiap deploy menjalankan image bertag SHA commit, jadi image lama masih ada di GHCR. Ganti baris IMAGE di .env server ke tag yang lebih lama lalu jalankan docker compose up -d --wait. Tidak ada yang perlu di-build ulang.

---

- HTML version: https://www.matthewswong.com/id/blog/cara-deploy-nextjs-ke-vps-docker-github-actions/
- English: https://www.matthewswong.com/en/blog/cara-deploy-nextjs-ke-vps-docker-github-actions/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
