Cara Install Nextcloud di VPS dengan Docker
Panduan instalasi Nextcloud menggunakan Docker Compose, Postgres, dan Redis. Pelajari cara konfigurasi TLS serta strategi backup data agar tetap aman.
Apa yang sebenarnya Anda bangun
Panduan ini menjalankan Nextcloud pada VPS menggunakan Docker Compose, memasang TLS Let's Encrypt di depannya, dan menyiapkan pencadangan yang dapat dipulihkan. Terdiri dari empat kontainer dan satu proxy: image nextcloud resmi yang mendengarkan pada loopback, Postgres untuk menyimpan seluruh metadata file, Redis untuk menyimpan file locks, salinan kedua dari image Nextcloud yang hanya menjalankan loop cron, dan nginx pada host untuk terminasi TLS di depannya. Proses instalasi memakan waktu dua puluh menit, namun itu bukan bagian yang terpenting. Dua keputusan pada jam pertama menentukan apakah file Anda tetap aman dalam satu tahun: menggunakan database asli alih-alih SQLite, dan pencadangan yang menangkap direktori data, database, serta config.php sebagai satu kesatuan yang konsisten.
Ini mengasumsikan penggunaan Ubuntu 24.04 LTS atau Debian 13, Docker Engine dengan plugin Compose v2 yang diinstal dari repositori Docker, dan catatan DNS A (serta AAAA jika Anda menggunakan IPv6) yang sudah mengarah ke cloud.example.com pada VPS. Semua ini memerlukan server yang Anda kendalikan — tidak ada cara untuk melakukan terminasi TLS dan dump database pada layanan SaaS milik orang lain.
Sizing: apa yang sebenarnya mengonsumsi memori
Penggunaan memori Nextcloud didominasi oleh tiga hal, dan tidak ada satu pun yang merupakan "Nextcloud" itu sendiri.
PHP workers. Image -apache melayani setiap permintaan konkuren dari proses worker yang memegang interpreter PHP. Setiap worker dapat tumbuh hingga PHP_MEMORY_LIMIT sebelum PHP menghentikan permintaan tersebut. Penggunaan memori residen terburuk Anda kira-kira adalah jumlah permintaan konkuren × batas memori, dan klien sinkronisasi desktop membuka beberapa koneksi paralel per pengguna. Konkurensi, bukan jumlah pengguna, yang menentukan batas atasnya.
Database. Postgres membuat fork backend per koneksi dan menjaga shared buffers tetap residen. Working set-nya berskala sesuai dengan jumlah file, bukan jumlah byte: oc_filecache menyimpan satu baris per file per pengguna. Seratus ribu file kecil menghasilkan database yang lebih berat daripada seratus file besar.
Pembuatan preview. Membuat thumbnail mendekode gambar sumber ke dalam memori pada resolusi penuh. Preview video menjalankan perintah ke ffmpeg. Menjalankan occ preview:generate-all melakukan lonjakan tersebut secara berulang dan berturut-turut, dan merupakan cara paling umum yang menyebabkan VPS kecil terkena OOM killer.
Redis relatif murah. Apa pun yang Anda tambahkan kemudian — Collabora, full-text search, pemindai antivirus — adalah layanan residen terpisah dengan jejak memori sendiri, dan harus masuk dalam rencana sizing Anda sebelum Anda mengaktifkannya.
Langkah penyesuaian, jika RAM Anda terbatas: turunkan PHP_MEMORY_LIMIT, batasi preview_max_x / preview_max_y / preview_max_filesize_image, batasi enabledPreviewProviders hanya pada format yang benar-benar Anda buka, dan atur trashbin_retention_obligation serta versions_retention_obligation agar direktori data tidak tumbuh secara diam-diam hingga beberapa kali lipat dari ukuran file Anda. Tambahkan swap file. Swap itu lambat, dan OOM kill di tengah proses upgrade jauh lebih buruk.
Mengapa SQLite bermasalah
Nextcloud menyertakan dukungan SQLite dan image resmi akan menggunakannya secara otomatis. Jangan gunakan SQLite. SQLite melakukan serialisasi penulisan dengan kunci seluruh database: hanya satu penulis dalam satu waktu untuk seluruh file. Nextcloud melakukan penulisan secara terus-menerus — kunci file, baris aktivitas, entri cache, status pekerjaan — dan satu desktop client yang menyinkronkan struktur direktori akan mengirimkan banyak permintaan paralel. Dalam pola tersebut, Anda akan mengalami SQLSTATE[HY000]: General error: 5 database is locked dan HTTP 500, dan kegagalan terjadi tepat saat instance mulai digunakan secara intensif.
Konversi di kemudian hari dapat dilakukan dengan occ db:convert-type, tetapi ini adalah migrasi data langsung yang memakan waktu lama dan bersifat mutlak. Mulailah dengan Postgres atau MariaDB.
File Compose
Simpan ini di /srv/nextcloud/compose.yaml, dengan rahasia (secrets) di file .env yang sejajar pada mode 600.
services:
db:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- db:/var/lib/postgresql/data
environment:
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${DB_PASSWORD}
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
app:
image: nextcloud:31-apache
restart: unless-stopped
depends_on: [db, redis]
ports:
- "127.0.0.1:8080:80"
volumes:
- html:/var/www/html
- /srv/nextcloud/data:/var/www/html/data
environment:
POSTGRES_HOST: db
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${DB_PASSWORD}
REDIS_HOST: redis
REDIS_HOST_PASSWORD: ${REDIS_PASSWORD}
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: cloud.example.com
TRUSTED_PROXIES: 172.16.0.0/12
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://cloud.example.com
APACHE_DISABLE_REWRITE_IP: "1"
PHP_MEMORY_LIMIT: 512M
PHP_UPLOAD_LIMIT: 10G
cron:
image: nextcloud:31-apache
restart: unless-stopped
entrypoint: /cron.sh
depends_on: [db, redis]
volumes:
- html:/var/www/html
- /srv/nextcloud/data:/var/www/html/data
volumes:
db:
html:Gunakan tag utama (major tag) dan periksa tag saat ini di Docker Hub sebelum Anda menyalin 31 secara verbatim. latest dapat menyebabkan pembaruan ke versi mayor pada docker compose pull mendatang, dan Nextcloud tidak mendukung hal tersebut.
Direktori data menggunakan bind mount, bukan named volume, dengan sengaja: jalur yang dapat diarahkan langsung oleh alat cadangan (backup tool) lebih berharga daripada kerapian. Buat direktori tersebut dengan UID www-data dari image dan izin (permissions) yang diminta Nextcloud:
sudo mkdir -p /srv/nextcloud/data
sudo chown -R 33:33 /srv/nextcloud/data
sudo chmod 0770 /srv/nextcloud/dataPerhatikan publikasi port: 127.0.0.1:8080:80. Docker mempublikasikan port dengan menulis aturan DNAT yang dievaluasi sebelum rantai INPUT milik ufw melihat paket tersebut — penggunaan 8080:80 secara langsung akan mengekspos Nextcloud yang tidak terenkripsi ke internet publik terlepas dari pengaturan ufw. Mengikat (binding) ke loopback akan menjaganya tetap terpisah dari antarmuka publik. Dengan demikian, firewall hanya perlu mengizinkan proxy — dan jika Anda tidak ingin membiarkan SSH terbuka untuk seluruh internet, mengakses VPS melalui VPN WireGuard mandiri memungkinkan Anda menghapus port 22 dari aturan publik sepenuhnya:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableJalankan dengan docker compose up -d, lalu pantau docker compose logs -f app. Booting pertama akan menyalin seluruh struktur aplikasi ke dalam volume dan menjalankan installer; kontainer tidak akan merespons apa pun hingga proses tersebut selesai.
TLS dan reverse proxy
Instal nginx dan certbot dari distro, buat server block port-80 biasa dengan server_name yang tepat, lalu biarkan certbot menulis ulangnya. Mekanisme tantangan HTTP-01, timer pembaruan, dan mode kegagalan dibahas secara lengkap dalam menerbitkan sertifikat Let's Encrypt dengan certbot dan nginx di Ubuntu 24.04:
sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d cloud.example.comCertbot menambahkan baris ssl_certificate dan pengalihan :80 → :443, serta menginstal timer systemd untuk memperbarui sertifikat 90 hari tersebut. Konfirmasi keberadaannya dengan systemctl list-timers | grep certbot — timer pembaruan yang tidak diaktifkan akan menyebabkan sertifikat kedaluwarsa dalam 90 hari.
Blok proxy itu sendiri:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name cloud.example.com;
# certbot manages ssl_certificate / ssl_certificate_key here
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
client_max_body_size 10G;
client_body_timeout 300s;
location = /.well-known/carddav { return 301 /remote.php/dav; }
location = /.well-known/caldav { return 301 /remote.php/dav; }
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}Pada nginx 1.25 dan versi terbaru, tambahkan http2 on;. Ubuntu 24.04 menggunakan build lama di mana padanannya adalah listen 443 ssl http2;. nginx -t akan memberi tahu Anda mana yang diterima oleh build Anda.
client_max_body_size dan long read timeouts adalah hal yang mencegah unggahan besar gagal di tengah jalan. proxy_request_buffering off mengalirkan unggahan secara langsung alih-alih menyimpan seluruh file ke disk proxy terlebih dahulu.
nginx pada host adalah cara termudah untuk satu aplikasi. Jika Nextcloud akan berbagi VPS dengan kontainer lain, menjalankan Traefik sebagai reverse proxy Docker Compose untuk beberapa aplikasi memindahkan perutean dan penerbitan sertifikat ke dalam label kontainer, dan masalah client_max_body_size serta timeout yang sama akan muncul kembali di sana sebagai pengaturan middleware dan transport.
trusted_proxies dan overwriteprotocol
Di sinilah sebagian besar instalasi Nextcloud self-hosted mengalami kesalahan, dan gejalanya tampak tidak berhubungan dengan penyebabnya.
X-Forwarded-Proto: https hanya diakui jika permintaan datang dari alamat yang terdaftar di trusted_proxies. Jika tidak diakui, Nextcloud menganggap permintaan adalah HTTP biasa dan menghasilkan URL http://; proxy mengalihkan URL tersebut ke HTTPS; browser mengikuti; Nextcloud menghasilkan http:// lagi. Itulah penyebab loop pengalihan (redirect loop). OVERWRITEPROTOCOL: https akan mengunci skema terlepas dari kondisi tersebut.
Kesalahan pada TRUSTED_PROXIES adalah alamat yang dilihat Nextcloud bukan 127.0.0.1. nginx berjalan di host dan terhubung ke port yang dipublikasikan, sehingga kontainer melihat Docker bridge gateway — sesuatu di dalam 172.x. Cari subnet yang sebenarnya:
docker network inspect nextcloud_default \
-f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'Masukkan CIDR tersebut (atau 172.16.0.0/12 yang mencakupnya) ke dalam TRUSTED_PROXIES. Jika diatur terlalu luas, klien mana pun dapat memalsukan X-Forwarded-For; jika salah, setiap login akan terlihat berasal dari alamat gateway, perlindungan brute-force akan memblokir seluruh instance Anda, dan ringkasan admin akan menampilkan "The reverse proxy header configuration is incorrect, or you are accessing Nextcloud from a trusted proxy."
OVERWRITECLIURL penting untuk kontainer cron, yang tidak memiliki permintaan masuk untuk menyimpulkan hostname. Tanpa pengaturan ini, pekerjaan latar belakang (background jobs) menghasilkan tautan ke localhost dan notifikasi email mengirimkan URL yang tidak dapat digunakan.
Pekerjaan latar belakang: cron, bukan AJAX
Pelaksana tugas default Nextcloud adalah AJAX: tugas dijalankan sebagai efek samping saat seseorang memuat halaman. Tidak ada pengguna yang menjelajah pada pukul 04:00, sehingga penghapusan sampah, pembersihan versi, pratinjau, dan percobaan ulang federated tertunda. Gejala pertamanya adalah direktori data yang terus membesar tanpa henti. Layanan cron di atas menjalankan loop /cron.sh resmi pada volume yang sama. Beritahu Nextcloud untuk menggunakannya:
docker compose exec -u www-data app php occ background:cronSetiap perintah occ mengikuti format tersebut: docker compose exec -u www-data app php occ <command>. Disarankan untuk membuat alias.
Backups: tiga hal, atau tidak sama sekali
Backup hanya filesystem akan gagal saat restorasi pada instance yang rusak. Direktori data menyimpan byte; Postgres menyimpan cache file, share, user, dan state aplikasi; config.php menyimpan kredensial database, instance ID, dan password salt. Jika Anda merestorasi file tanpa database, Nextcloud tidak dapat membacanya. Jika Anda merestorasi database tanpa config.php, database tidak dapat dibuka. Merestorasi database lama ke direktori data yang lebih baru akan menyebabkan share merujuk ke file yang telah berpindah lokasi.
Cadangkan ketiga komponen tersebut dari instance yang sedang dalam kondisi quiesced:
#!/usr/bin/env bash
set -euo pipefail
cd /srv/nextcloud
DEST="/var/backups/nextcloud/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$DEST"
occ() { docker compose exec -T -u www-data app php occ "$@"; }
occ maintenance:mode --on
trap 'occ maintenance:mode --off' EXIT
docker compose exec -T db \
pg_dump -U nextcloud --clean --if-exists nextcloud | gzip > "$DEST/db.sql.gz"
docker compose exec -T app \
tar -C /var/www/html -cf - config custom_apps themes > "$DEST/app.tar"
rsync -a --delete /srv/nextcloud/data/ /var/backups/nextcloud/data/Maintenance mode memastikan dump database dan salinan file sinkron. Jika dilewati, Anda akan mendapatkan database yang merujuk ke file yang belum tersalin oleh rsync. Perlu dicatat bahwa skrip ini menyimpan dump database dengan timestamp, tetapi hanya menyimpan satu mirror berulang untuk direktori data — rsync --delete menimpanya setiap kali dijalankan — sehingga hanya dump terbaru yang cocok dengan salinan file tersebut.
Setelah itu, pindahkan cadangan keluar dari server. Cadangan yang berada di VPS yang sama dengan data yang dicadangkan hanyalah salinan, bukan backup. Menggunakan restic ke object storage atau host kedua adalah solusi umum, dan fitur deduplikasi pada alat tersebut menangani direktori data jauh lebih baik daripada tarball harian. Pengaturan lengkap, mulai dari inisialisasi repositori hingga timer harian dan latihan restorasi, tersedia di off-box VPS backups with restic.
Restorasi bukan sekadar proses kebalikan. Stack yang baru dimulai akan menjalankan installer dan menulis config.php baru — instance ID dan password salt baru — dan mengimpor dump di atas identitas baru tersebut akan menyebabkan sesi dan token share rusak. Kembalikan identitas lama terlebih dahulu, dengan urutan berikut:
docker compose up -d && docker compose stop app cron # create the volumes, then halt the app
sudo rsync -a --delete /var/backups/nextcloud/data/ /srv/nextcloud/data/
docker compose run --rm -T --entrypoint "" app \
tar -C /var/www/html -xf - < app.tar # the original config.php returns
gunzip -c db.sql.gz | docker compose exec -T db psql -U nextcloud -d nextcloud
docker compose start app cron
docker compose exec -T -u www-data app php occ maintenance:mode --off
docker compose exec -T -u www-data app php occ files:scan --allfiles:scan menyinkronkan cache file dengan data yang sebenarnya ada di disk. Lakukan latihan ini satu kali pada VPS cadangan sebelum Anda benar-benar membutuhkannya.
Upgrades: satu versi mayor dalam satu waktu
Nextcloud hanya mendukung pembaruan satu versi mayor dalam satu waktu. Melompati versi dari 29 ke 31 akan menyebabkan kegagalan — sistem akan error dengan Exception: Updates between multiple major versions and downgrades are unsupported. dan masuk ke mode pemeliharaan (maintenance mode).
Prosedur pembaruan Docker adalah: buat cadangan (backup), ubah tag dari 31 ke 32 pada layanan app dan cron, lalu jalankan docker compose pull && docker compose up -d, kemudian docker compose logs -f app. Image entrypoint akan mendeteksi kode baru terhadap data yang ada dan menjalankan occ upgrade secara otomatis. Jangan menghentikan proses tersebut. Setelah log berhenti bergerak, jalankan docker compose exec -u www-data app php occ status dan periksa versionstring serta pastikan aplikasi telah aktif kembali.
Dua aturan untuk menghindari masalah: naikkan satu versi mayor, verifikasi, lalu naikkan versi berikutnya. Jangan pernah mengubah tag pada layanan app tanpa mengubah cron agar sesuai — menggunakan dua versi Nextcloud yang berbeda pada satu database yang sama akan menyebabkan korupsi data.
Kesalahan yang akan Anda temui
"Your data directory is readable by other users. Please change the permissions to 0770." Direktori bind-mount memiliki izin baca untuk grup atau user lain. sudo chmod 0770 /srv/nextcloud/data dan sudo chown -R 33:33 /srv/nextcloud/data.
"Your data directory is invalid. Ensure there is a file called .ocdata in the root." Bind mount mengarah ke lokasi yang belum diinisialisasi oleh Nextcloud — kesalahan pengetikan pada path, atau direktori kosong baru menggantikan direktori yang sudah berfungsi. Pastikan path host sesuai dengan baris volume.
"Access through untrusted domain." Hostname dalam request tidak terdaftar di trusted_domains. NEXTCLOUD_TRUSTED_DOMAINS hanya berlaku pada instalasi pertama; setelah itu, atur secara live: occ config:system:set trusted_domains 1 --value=cloud.example.com.
502 Bad Gateway, dengan connect() failed (111: Connection refused) while connecting to upstream di /var/log/nginx/error.log. nginx tidak menemukan layanan pada 127.0.0.1:8080. Kontainer masih dalam proses inisialisasi (periksa docker compose logs app), kontainer berhenti (docker compose ps), atau baris publish tidak sesuai dengan port proxy_pass. Konfirmasi dengan ss -ltnp | grep 8080.
Redirect loop, atau peringatan "insecure" pada admin overview. OVERWRITEPROTOCOL: https hilang, atau TRUSTED_PROXIES tidak berisi subnet gateway Docker. Lihat bagian proxy di atas.
LockedException: "files/..." is locked. Jika REDIS_HOST diatur, image akan mengonfigurasi Redis sebagai backend locking sehingga stale locks jarang terjadi. Tanpa itu, lock disimpan dalam tabel database oc_file_locks dan request yang terhenti saat proses tulis akan meninggalkan baris data. Pastikan Redis benar-benar digunakan — occ config:system:get memcache.locking harus mengembalikan class Redis — sebelum Anda menghapus baris lock secara manual.
"The PHP memory limit is below the recommended value of 512MB." Tingkatkan PHP_MEMORY_LIMIT dan buat ulang kontainer. Ingat dampak pengaturan tersebut terhadap batas maksimal penggunaan memori Anda.
Apa yang menghambat skalabilitas
Kendala pertama adalah direktori data yang melebihi kapasitas volume. Memperbesar volume pada VPS memerlukan proses resize dan filesystem grow. Proses ini jauh lebih sulit dijadwalkan saat kapasitas sudah mencapai 100% — segera pasang peringatan penggunaan disk sebelum penuh.
Kendala kedua adalah oc_filecache. Proses listing file dan pemindaian sinkronisasi melambat seiring bertambahnya jumlah baris. Solusinya adalah optimasi database: simpan Postgres pada penyimpanan cepat, berikan memori bersama yang cukup, dan hapus data sampah serta versi lama menggunakan pengaturan retensi agar tidak menumpuk selamanya.
Kendala ketiga adalah pembuatan pratinjau yang memperebutkan sumber daya dengan proses lainnya. Pada server kecil, batasi penyedia pratinjau dan jangan pernah menjalankan occ preview:generate-all selama jam kerja.
Selain itu, jawaban jujurnya adalah fitur tambahan membutuhkan mesin sendiri. Collabora dan pencarian teks lengkap adalah layanan residen terpisah dengan profil memori sendiri. Menempatkan fitur tersebut pada mesin yang sama dengan penyimpanan file utama akan memperluas domain kegagalan tanpa memberikan keuntungan. Pindahkan penyimpanan file ke penyimpanan utama yang kompatibel dengan S3 saat volume tidak lagi memadai — perlu dicatat bahwa hal ini membuat pencadangan menjadi lebih sulit, bukan lebih mudah: database tetap menyimpan metadata, dan database tersebut harus dicadangkan bersamaan dengan bucket.
Setelah instance melayani pengguna nyata, pasang Uptime Kuma di depannya agar Anda mengetahui adanya downtime sebelum klien sinkronisasi mengetahuinya. Cloud pribadi sangat cocok dipadukan dengan server email sendiri, dan jika Anda tidak ingin menghubungkan layanan secara manual, Cloudron, CasaOS dan Coolify adalah perbandingan platform yang melakukannya untuk Anda.
FAQ
Bisakah saya menjalankan Nextcloud menggunakan SQLite alih-alih Postgres?
Bisa, dan image resmi mendukung hal tersebut, tetapi satu desktop sync client yang mengirimkan permintaan paralel akan menyebabkan SQLSTATE[HY000]: General error: 5 database is locked dan HTTP 500. SQLite menggunakan database-wide write lock, sedangkan Nextcloud melakukan penulisan secara terus-menerus — file locks, baris activity, dan status job. Gunakan Postgres atau MariaDB sejak awal; occ db:convert-type tersedia tetapi proses migrasi data live bersifat panjang dan bersifat all-or-nothing.
Berapa banyak RAM yang sebenarnya dibutuhkan oleh VPS Nextcloud?
Ukuran RAM ditentukan oleh konkurensi, bukan jumlah pengguna. Penggunaan memori resident terburuk adalah perkiraan jumlah permintaan konkuren dikalikan PHP_MEMORY_LIMIT, ditambah Postgres shared buffers dan satu backend per koneksi, ditambah lonjakan saat pembuatan preview. Server 2 GB dapat menjalankan instance rumah tangga kecil jika Anda membatasi preview dan menambahkan swap; jika Anda menambahkan Collabora atau full-text search, Anda memerlukan kapasitas untuk set layanan resident tambahan.
Mengapa unggahan besar gagal saat menggunakan nginx reverse proxy?
Dua pengaturan pada proxy biasanya menjadi penyebabnya: client_max_body_size yang dibiarkan pada default 1 MB akan memutus permintaan, dan nilai proxy_read_timeout / proxy_send_timeout yang terlalu pendek akan menghentikan transfer panjang di tengah jalan. Atur keduanya dengan nilai yang besar, ubah proxy_request_buffering off menjadi stream alih-alih spool, dan naikkan PHP_UPLOAD_LIMIT pada container aplikasi agar sesuai.
Mengapa Nextcloud mengalami redirect loop atau memberikan peringatan tentang reverse proxy?
Container tidak melihat nginx pada 127.0.0.1 — container melihat Docker bridge gateway di 172.x. Jika alamat tersebut tidak ada dalam TRUSTED_PROXIES, header X-Forwarded-Proto: https akan diabaikan, Nextcloud akan menghasilkan URL http://, dan proxy akan mengembalikannya kembali. Atur TRUSTED_PROXIES ke subnet bridge yang sebenarnya dan tetapkan OVERWRITEPROTOCOL: https.
Bisakah saya memperbarui Nextcloud dari versi 29 langsung ke 31?
Tidak bisa. Nextcloud hanya mendukung satu versi mayor per pembaruan, dan melompati versi akan berhenti pada Updates between multiple major versions and downgrades are unsupported., sehingga instance masuk ke maintenance mode. Lakukan backup, naikkan tag satu versi mayor pada layanan app dan cron, lakukan docker compose pull && docker compose up -d, verifikasi dengan occ status, lalu ulangi prosesnya.