Setiap kali kamu redeploy aplikasi di Helipod, container lama dihapus dan container baru dibuat dari image terbaru. Ini bagus untuk konsistensi — tapi punya konsekuensi: semua file yang dibuat di dalam container akan hilang.
Upload foto dari user? Hilang. Cache yang sudah dibangun? Hilang. Database SQLite? Hilang.
Solusinya adalah Persistent Volume — storage yang tetap ada bahkan setelah container dihapus dan dibuat ulang, dan di-mount ke container di path yang kamu tentukan.
Ephemeral vs Persistent Storage
Ephemeral Storage (default): File yang dibuat di dalam container disimpan di layer container. Saat container dihapus (setiap redeploy), file-file ini ikut hilang.
Persistent Volume: Storage terpisah yang di-mount ke container. Tidak ikut hilang saat container dihapus. Tersedia lagi di container baru saat redeploy.
Analoginya: container seperti kamar hotel — setiap kamu check out, semua yang ada di kamar dibersihkan. Persistent volume seperti loker penyimpanan di lobby — tetap ada meski kamu ganti kamar.
Cara Setup Persistent Volume
Via Dashboard
- Buka service di dashboard Helipod
- Buka tab Settings
- Scroll ke Storage Volumes → Mounted Volumes
- Klik + Add Volume
- Isi:
- Mount Path — path di dalam container tempat volume di-mount
- Size — ukuran storage yang dibutuhkan (GB)
- Klik Save dan Redeploy
Via helipack.json
{
"volume": {
"mountPath": "/app/storage",
"size": 10
}
}
Penting: Setelah volume di-mount, isi volume tidak akan hilang saat redeploy. Tapi jika kamu menghapus volume, semua data hilang permanen dan tidak bisa dipulihkan.
Use Case: Laravel Storage
Laravel menyimpan uploaded files, generated files, dan cache di folder storage/. Secara default, ini ephemeral. Untuk persistent:
Setup
Di Settings → Storage Volumes → Add Volume:
- Mount Path:
/var/www/html/storage - Size: 10 GB (atau sesuai kebutuhan)
Atau di helipack.json:
{
"volume": {
"mountPath": "/var/www/html/storage",
"size": 10
}
}
Konfigurasi Laravel
# .env
FILESYSTEM_DISK=local
// config/filesystems.php
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
],
Setup Storage Link
Setelah volume di-mount dan deploy pertama, jalankan dari terminal:
php artisan storage:link
Ini membuat symlink dari public/storage ke storage/app/public.
Catatan:
storage:linkperlu dijalankan ulang setiap kali ada redeploy karena symlink ada di container layer yang ephemeral, bukan di volume. Tambahkan kehelipack.jsonuntuk otomatis:
{
"run": {
"before": "php artisan storage:link && php artisan migrate --force"
},
"volume": {
"mountPath": "/var/www/html/storage",
"size": 10
}
}
Upload File di Laravel
// Controller
public function upload(Request $request)
{
$request->validate([
'file' => 'required|file|max:10240',
]);
$path = $request->file('file')->store('uploads', 'public');
return response()->json([
'path' => $path,
'url' => Storage::url($path),
]);
}
File akan disimpan di storage/app/public/uploads/ — yang ada di persistent volume, tidak hilang saat redeploy.
Use Case: Django Media Files
Django menyimpan uploaded files di MEDIA_ROOT. Untuk persistent:
Mount path: /app/media
# settings.py
import os
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
# urls.py (untuk serve media di development/production sederhana)
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
# ... url patterns
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200)
image = models.ImageField(upload_to='products/') # tersimpan di media/products/
Untuk production yang serius: Pertimbangkan untuk serve media files via object storage (AWS S3, DigitalOcean Spaces, Cloudflare R2) menggunakan
django-storages— lebih scalable dan tidak tergantung persistent volume.
Use Case: SQLite Database
SQLite cocok untuk aplikasi kecil atau yang tidak membutuhkan PostgreSQL. Karena database adalah satu file, harus ada di persistent volume agar tidak hilang.
Mount path: /app/data
Django:
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': '/app/data/db.sqlite3', # di dalam persistent volume
}
}
FastAPI + SQLAlchemy:
DATABASE_URL = "sqlite+aiosqlite:////app/data/myapp.db"
engine = create_async_engine(DATABASE_URL)
Flask:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////app/data/myapp.db'
Catatan: SQLite tidak cocok untuk multi-replica deployment karena tidak mendukung concurrent writes dari multiple processes. Jika butuh replicas, gunakan PostgreSQL.
Use Case: Node.js / Next.js Cache
Beberapa aplikasi Node.js menyimpan cache di disk:
Next.js ISR Cache:
{
"volume": {
"mountPath": "/app/.next/cache",
"size": 5
}
}
Ini membuat Next.js Incremental Static Regeneration cache persist antar redeploy — mempercepat load time setelah deployment.
Puppeteer / Playwright Browser Cache:
{
"volume": {
"mountPath": "/root/.cache/puppeteer",
"size": 2
}
}
Use Case: Python / ML Model Files
Untuk ML model serving, model files sering berukuran besar (100MB–10GB). Simpan di persistent volume agar tidak perlu download ulang setiap redeploy.
{
"volume": {
"mountPath": "/app/models",
"size": 20
},
"run": {
"before": "python scripts/download_model.py"
},
"health": {
"path": "/health",
"duration": 60
}
}
# scripts/download_model.py
import os
from pathlib import Path
MODEL_PATH = Path("/app/models/model.bin")
if not MODEL_PATH.exists():
print("Downloading model...")
# download dari HuggingFace, S3, atau URL lain
import urllib.request
urllib.request.urlretrieve(
"https://huggingface.co/yourmodel/model.bin",
MODEL_PATH
)
print("Model downloaded!")
else:
print("Model already exists, skipping download.")
Dengan pola ini, model hanya di-download saat volume kosong (deploy pertama atau setelah volume di-reset). Redeploy berikutnya langsung pakai model yang sudah ada di volume.
Backup Data di Persistent Volume
Helipod tidak menyediakan automatic backup untuk persistent volume saat ini. Kamu perlu setup backup strategy sendiri.
Opsi 1: Backup ke Object Storage (Direkomendasikan)
# Dari terminal Helipod, install rclone
# atau gunakan aws s3 sync / s3cmd
# Contoh backup ke DigitalOcean Spaces
s3cmd sync /app/storage/ s3://mybucket/backups/$(date +%Y%m%d)/
# Atau ke Cloudflare R2
aws s3 sync /app/storage/ s3://mybucket/backups/ \
--endpoint-url https://xxx.r2.cloudflarestorage.com
Opsi 2: PostgreSQL Dump (untuk data penting)
Jangan simpan data penting hanya di SQLite file. Untuk data yang critical, gunakan PostgreSQL dengan backup rutin:
# Dari terminal
pg_dump $DATABASE_URL | gzip > /app/data/backup_$(date +%Y%m%d).sql.gz
Troubleshooting
Volume tidak ter-mount / path tidak ada
Cek apakah volume sudah berhasil ditambahkan di Settings. Dari terminal:
df -h | grep volume
mount | grep /app/storage
ls -la /app/storage/
Permission denied saat menulis ke volume
Volume di-mount dengan permission root. Jika aplikasi berjalan sebagai non-root user (best practice Helipod), perlu set permission:
# Dari terminal
sudo chown -R appuser:appgroup /app/storage
# Atau
chmod -R 777 /app/storage # kurang aman, tapi cepat untuk debug
Solusi lebih permanen: tambahkan di helipack.json:
{
"run": {
"before": "chown -R www-data:www-data /var/www/html/storage && php artisan migrate --force"
}
}
Data hilang padahal sudah setup volume
Pastikan mount path di Settings sama persis dengan path yang dipakai aplikasi. Perbedaan trailing slash atau typo bisa menyebabkan aplikasi menulis ke path yang salah (ephemeral layer, bukan volume).
# Verifikasi dari terminal
ls -la /app/storage/ # harus ada file yang kamu upload sebelumnya
Keterbatasan Persistent Volume
Single-pod only: Volume di-mount ke satu pod. Jika kamu menjalankan multiple replicas, semua replica akan mount volume yang sama — ini bisa menyebabkan konflik jika ada concurrent writes. Untuk multi-replica deployment, gunakan object storage (S3/R2/Spaces) sebagai ganti filesystem local.
Not for databases dengan high write: Volume persistent cocok untuk SQLite dengan traffic rendah. Untuk PostgreSQL atau database lain dengan write yang sering, gunakan pod database dedicated yang sudah dioptimasi.
Manual backup: Tidak ada automatic snapshot atau point-in-time recovery. Setup backup sendiri untuk data yang critical.
Kesimpulan
Persistent Volume di Helipod menyelesaikan masalah klasik container: "file hilang saat redeploy." Untuk uploaded files, ML models, SQLite databases, dan cache yang butuh persist — mount volume di path yang tepat dan selesai.
Untuk aplikasi yang butuh scalability lebih tinggi (multi-replica), pertimbangkan pindah file storage ke object storage eksternal sambil menggunakan PostgreSQL untuk data terstruktur.
Ingin tahu lebih tentang deploy PostgreSQL di Helipod? Baca Panduan Deploy PostgreSQL di Helipod.
Punya pertanyaan? Hubungi kami di support@helipod.id atau bergabung ke komunitas di hangar.helipod.io.