Daily Docker Backup — demo19 / Epris

Panduan lengkap stack backup Portainer (demo19-db + demo19-odoo): deploy, env, backup, restore DB/full, jadwal, dan cara membuat script baru dari pola yang sama. Snippet di bawah bisa Copy atau Edit langsung di halaman ini.

Lokal retention pg_dump + volume tar Cloud upload: fase berikutnya

Lokasi script: ~/scripts/v19_epris_docker_backup/
Plan: ~/.cursor/plans/v19_epris_daily_docker_backup.plan.md
Deploy tipikal di host: /opt/odoo-backup-demo19/ · backup root /var/backups/odoo-demo19/

1. Overview

Backup harian tidak mengandalkan tar mentah volume Postgres yang sedang hidup. Sumber kebenaran DB adalah logical dump; volume Odoo di-arsip read-only.

1
pg_dump dari demo19-db → db.dump.gz
2
tar volume data_dir, customs, config, enterprise (helper Alpine, mount :ro)
3
MANIFEST + SHA256SUMS + symlink latest
4
prune folder harian > KEEP_DAILY
VolumeIsiCara backup
odoo_db_dataPG data dirLewat pg_dump (tar mentah opsional, default off)
odoo_data_dirFilestoretar harian
odoo_customsCustom addonstar harian
odoo_configodoo.conftar harian
odoo_enterpriseEE addonstar harian
Nama volume Portainer sering ber-prefix stack (mis. demo19_odoo_data_dir). Selalu cocokkan dengan docker volume ls di backup.env.

2. Struktur folder

tree · v19_epris_docker_backup/ editing
v19_epris_docker_backup/
├── bin/
│   ├── backup-demo19.sh          # job harian
│   ├── prune-demo19-backups.sh   # retention
│   ├── restore-demo19-db.sh      # restore Postgres saja
│   └── restore-demo19-full.sh    # DB + 4 volume Odoo
├── config/
│   └── backup.env.example        # → copy jadi backup.env (chmod 600)
├── systemd/
│   ├── odoo-demo19-backup.service
│   └── odoo-demo19-backup.timer
├── docs/
│   └── index.html                # dokumentasi ini
└── README.md

3. Quick start (host Portainer)

bash · install + uji manual editing
sudo mkdir -p /opt/odoo-backup-demo19 /var/backups/odoo-demo19 /var/log
sudo cp -a ~/scripts/v19_epris_docker_backup/. /opt/odoo-backup-demo19/

sudo cp /opt/odoo-backup-demo19/config/backup.env.example \
        /opt/odoo-backup-demo19/config/backup.env
sudo chmod 600 /opt/odoo-backup-demo19/config/backup.env
sudo chmod 700 /opt/odoo-backup-demo19/bin/*.sh

# Sesuaikan nama container & volume di host ini:
docker ps --format '{{.Names}}'
docker volume ls
sudo nano /opt/odoo-backup-demo19/config/backup.env

# Uji manual
sudo /opt/odoo-backup-demo19/bin/backup-demo19.sh
ls -lah /var/backups/odoo-demo19/latest/
sha256sum -c /var/backups/odoo-demo19/latest/SHA256SUMS

4. Config backup.env

Secret dan nama resource hidup di env, bukan di script. Backup: argumen posisi path env. Restore: --env-file PATH atau $BACKUP_ENV_FILE.

config/backup.env.example · edit sesuai stack Anda editing
# --- Identity ---
BACKUP_NAME=demo19
BACKUP_ROOT=/var/backups/odoo-demo19
KEEP_DAILY=7
MIN_FREE_GB=5

# --- Containers ---
ODOO_CONTAINER=demo19-odoo
DB_CONTAINER=demo19-db

# --- Postgres (kosongkan password agar baca dari container) ---
PGUSER=odoo
PGPASSWORD=
PGDATABASE=demo-odoo19
PGDUMP_FORMAT=custom

# --- Volumes (cocokkan docker volume ls) ---
VOL_DB_DATA=odoo_db_data
VOL_DATA_DIR=odoo_data_dir
VOL_CUSTOMS=odoo_customs
VOL_CONFIG=odoo_config
VOL_ENTERPRISE=odoo_enterprise

STACK_COMPOSE_FILE=
PACK_BUNDLE=0
HELPER_IMAGE=alpine:3.20
STOP_ODOO_DURING_FILESTORE=0
# BACKUP_RAW_DB_VOLUME=0
VariabelArti
KEEP_DAILYJumlah folder tanggal yang disimpan
MIN_FREE_GBAbort jika free disk di bawah ambang
PGDUMP_FORMATcustom → db.dump.gz; plain → db.sql.gz
STOP_ODOO_DURING_FILESTORE1 = stop Odoo sebentar saat tar filestore
PACK_BUNDLE1 = buat demo19_YYYY-MM-DD.tar

5. Backup

bash · backup editing
# default: config/backup.env di tree script
sudo /opt/odoo-backup-demo19/bin/backup-demo19.sh

# atau path env eksplisit
sudo /opt/odoo-backup-demo19/bin/backup-demo19.sh \
  /opt/odoo-backup-demo19/config/backup.env
  1. Cek Docker daemon, container DB running, free disk
  2. Resolve PG* dari env file atau printenv di container DB
  3. pg_dump → gzip
  4. Tar 4 volume via docker run alpine … tar czf
  5. Tulis MANIFEST.txt, SHA256SUMS, update latest
  6. Panggil prune-demo19-backups.sh
bash · prune editing
sudo /opt/odoo-backup-demo19/bin/prune-demo19-backups.sh \
  /opt/odoo-backup-demo19/config/backup.env

6. Restore

Destructive. Full restore menghentikan Odoo, wipe isi volume target, lalu pg_restore --clean. Volume odoo_db_data tidak di-overwrite; DB dipulihkan lewat artifact dump.
bash · restore-demo19-full.sh editing
# folder tanggal atau symlink latest
sudo /opt/odoo-backup-demo19/bin/restore-demo19-full.sh \
  /var/backups/odoo-demo19/2026-10-21

# tanpa prompt (otomasi / drill)
sudo /opt/odoo-backup-demo19/bin/restore-demo19-full.sh \
  /var/backups/odoo-demo19/latest --yes

# env di lokasi non-default
sudo /opt/odoo-backup-demo19/bin/restore-demo19-full.sh \
  /var/backups/odoo-demo19/latest \
  --env-file /opt/odoo-backup-demo19/config/backup.env \
  --yes
bash · restore-demo19-db.sh editing
# minta ketik YES secara interaktif
sudo /opt/odoo-backup-demo19/bin/restore-demo19-db.sh \
  /var/backups/odoo-demo19/latest/db.dump.gz \
  --env-file /opt/odoo-backup-demo19/config/backup.env

# non-interaktif
printf 'YES\n' | sudo /opt/odoo-backup-demo19/bin/restore-demo19-db.sh \
  /var/backups/odoo-demo19/latest/db.dump.gz \
  --env-file /opt/odoo-backup-demo19/config/backup.env

DB-only tidak mengembalikan filestore/customs. Attachment hilang → pakai full restore.

FlagEfek
--yes / -ySkip prompt (full only)
--skip-dbHanya volume
--skip-volumesHanya database
--env-file PATHLokasi backup.env

7. Jadwal

cron · /etc/cron.d/odoo-demo19-backup editing
30 2 * * * root /opt/odoo-backup-demo19/bin/backup-demo19.sh >> /var/log/odoo-demo19-backup.log 2>&1
bash · enable timer editing
sudo cp /opt/odoo-backup-demo19/systemd/odoo-demo19-backup.* /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now odoo-demo19-backup.timer
systemctl list-timers | grep odoo-demo19
# uji sekali:
sudo systemctl start odoo-demo19-backup.service
journalctl -u odoo-demo19-backup.service -n 50 --no-pager

Timer default: 02:30 + RandomizedDelaySec=5m.

8. Verifikasi setelah backup / restore

bash · checks editing
LATEST=/var/backups/odoo-demo19/latest
ls -lah "$LATEST"
cat "$LATEST/MANIFEST.txt"
( cd "$LATEST" && sha256sum -c SHA256SUMS )

# Setelah restore full:
docker ps --filter name=demo19
# buka UI Odoo → apps/customs → sample attachment (filestore)

9. Membuat script baru dari pola ini

Kalau targetnya stack lain (bukan demo19), clone kerangka — jangan tulis dari nol.

bash · clone ke stack baru (contoh) editing
SRC=~/scripts/v19_epris_docker_backup
DST=~/scripts/v18_tlt_docker_backup   # ganti nama project
cp -a "$SRC" "$DST"
cd "$DST"

# rename file demo19 → tlt (contoh)
rename 's/demo19/tlt/' bin/* systemd/* 2>/dev/null || \
  for f in bin/*demo19* systemd/*demo19*; do
    mv "$f" "${f//demo19/tlt}"
  done

# edit identitas di env example
sed -i 's/demo19/tlt/g; s/odoo-demo19/odoo-tlt/g' config/backup.env.example
# lalu: cp config/backup.env.example config/backup.env && nano config/backup.env

10. Pola bash (template)

bash · skeleton script baru editing
#!/usr/bin/env bash
# Deskripsi singkat job.
# Usage: my-script.sh [/path/to/backup.env]
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
ENV_FILE="${1:-${ROOT_DIR}/config/backup.env}"

log() { printf '[%s] %s\n' "$(date -Is)" "$*"; }
die() { log "ERROR: $*"; exit 1; }

[[ -f "${ENV_FILE}" ]] || die "Missing env file: ${ENV_FILE}"
# shellcheck disable=SC1090
source "${ENV_FILE}"

: "${BACKUP_ROOT:?}"
: "${DB_CONTAINER:?}"
: "${ODOO_CONTAINER:?}"

command -v docker >/dev/null || die "docker CLI not found"
docker info >/dev/null 2>&1 || die "Cannot talk to Docker daemon"

log "=== start ==="
# … kerja di sini …
log "=== done ==="

Konvensi CLI

11. Sandbox testing

Di WSL tanpa Docker daemon, uji logika dengan mock docker + Postgres lokal (contoh drill: /tmp/epris_backup_sandbox).

1
Siapkan mock-bin di PATH, volume palsu, backup.env sandbox
2
Jalankan backup → cek artifact + sha256sum -c SHA256SUMS
3
Mutate DB & file volume → full restore → assert nilai lama kembali
4
Mutate DB lagi → DB-only restore (pipe YES) → assert
Hasil drill sandbox sebelumnya: backup, checksum, full restore, dan DB-only restore semua lulus. Sandbox memvalidasi script, bukan Docker engine produksi.
bash · contoh invoke sandbox editing
SANDBOX=/tmp/epris_backup_sandbox
SCRIPTS=~/scripts/v19_epris_docker_backup
export EPRIS_SANDBOX="$SANDBOX"
export PATH="$SANDBOX/mock-bin:$PATH"

"$SCRIPTS/bin/backup-demo19.sh" "$SANDBOX/backup.env"
"$SCRIPTS/bin/restore-demo19-full.sh" "$SANDBOX/backups/latest" \
  --env-file "$SANDBOX/backup.env" --yes
printf 'YES\n' | "$SCRIPTS/bin/restore-demo19-db.sh" \
  "$SANDBOX/backups/latest/db.dump.gz" --env-file "$SANDBOX/backup.env"

12. Isi satu hari backup

tree · /var/backups/odoo-demo19/ editing
/var/backups/odoo-demo19/
  2026-10-21/
    MANIFEST.txt
    SHA256SUMS
    backup.log
    db.dump.gz                 # atau db.sql.gz
    vol_odoo_data_dir.tar.gz
    vol_odoo_customs.tar.gz
    vol_odoo_config.tar.gz
    vol_odoo_enterprise.tar.gz
    stack.compose.yml          # jika STACK_COMPOSE_FILE di-set
  latest -> 2026-10-21/

13. CLI reference

CommandArgs utama
backup-demo19.sh [/path/to/backup.env]
prune-demo19-backups.sh [/path/to/backup.env]
restore-demo19-db.sh /path/to/db.dump.gz|db.sql.gz [--env-file PATH]
restore-demo19-full.sh /path/to/dated-dir [--env-file] [--yes] [--skip-db] [--skip-volumes]

14. Troubleshooting

GejalaCek
Missing env fileCopy example → backup.env; restore pakai --env-file
Cannot talk to Docker daemonUser di group docker / jalankan sebagai root; engine hidup
Volume not foundPrefix Portainer di docker volume ls vs env
PGPASSWORD emptyIsi di env atau pastikan POSTGRES_PASSWORD di container DB
pg_restore exit 1Sering benign dengan --clean; full script menerima 0 atau 1
Free disk abortTurunkan data / naikkan disk / sesuaikan MIN_FREE_GB
Attachment hilang setelah DB-onlyPakai restore-demo19-full.sh
Fase cloud: setelah daily lokal stabil 1–2 minggu, tambahkan upload (rclone / S3 / B2) + enkripsi di akhir backup — belum di-wire di script saat ini.