Skip to content

Zalo-TG Dev & Release Runbook

When to use: Develop new features, test on DEV (LucNguyen account), promote to PROD (CaoNguyen account on unraid), or triage bridge issues.

Estimated time: 2 min for DEV iteration, 5–10 min for PROD release (mostly Komodo Build time).

Source of truth: stacks/applications/zalo-tg/ in this repo (monorepo pattern, like openclaw, chat-adapter, nanobot).


Architecture recap

graph TD
    SRC["Source (this repo)
stacks/applications/zalo-tg/src/ ← TypeScript
stacks/applications/zalo-tg/Dockerfile ← Multi-stage Node 20"] SRC -->|git push origin main| BUILD["Komodo Build (auto-trigger on push)
linked_repo=docker-compose
Tags: :latest + :version-N + :commit-sha
→ Gitea: 100.68.251.84:3005/lucndm/zalo-tg"] BUILD --> DEV["DEV (local dev machine)
Account: LucNguyen · Container: zalo-tg
TG group: -1003869533735
Compose: docker-compose.dev.yml
Secrets: .env (gitignored) · manual build"] BUILD --> PROD["PROD (unraid via Komodo)
Account: CaoNguyen · Container: zalo-caonguyen
Compose: compose.yaml
Stack TOML: komodo/stacks/applications/zalo-tg.toml
auto_update=true"]

Two environments

DEV (local) PROD (unraid)
Container name zalo-tg zalo-caonguyen
Zalo account LucNguyen CaoNguyen
Compose file docker-compose.dev.yml compose.yaml
Secrets .env (gitignored) Komodo secrets (encrypted)
Data volume zalo-tg-data named volume /mnt/user/appdata/zalo-tg/caonguyen bind
Deploy trigger Manual (./docker-local.sh up) Auto on push (Komodo Stack auto_update=true)
Build source Local docker build . Komodo Build (linked_repo=docker-compose)

Scenario 1 — Iterate on DEV (LucNguyen, local)

When: Editing code, want fast feedback loop. No push needed.

cd /workspaces/github.com/minhluc-info/docker-compose/stacks/applications/zalo-tg

# Edit src/*.ts...

# Typecheck
docker run --rm -v "$PWD":/app -w /app node:20-alpine npm run build

# Build + recreate DEV container
./docker-local.sh up

# Watch boot (expect <2s, zero code 221)
docker logs -f zalo-tg

Verify: Send a test message from Zalo LucNguyen → check it appears in DEV Telegram group (-1003869533735).

Useful commands:

./docker-local.sh logs      # follow logs
./docker-local.sh restart   # restart container
./docker-local.sh down      # stop
./docker-local.sh shell     # exec sh into container

Scenario 2 — Promote to PROD (CaoNguyen, unraid)

When: DEV verified, ready to ship. Single push triggers full pipeline.

cd /workspaces/github.com/minhluc-info/docker-compose

# Stage + commit changes
git add stacks/applications/zalo-tg/
git commit -m "fix(zalo-tg): <description>"
git push origin main

What happens next (automatic):

  1. Komodo Sync detects push → updates repo on builder
  2. Komodo Build zalo-tg triggers → builds image → pushes :latest + version tags
  3. Komodo Stack zalo-tg (auto_update=true) detects new digest → pulls on unraid → recreates container

Verify PROD:

# Watch build progress
km list actions -n zalo-tg

# Watch prod boot
ssh root@100.68.251.84 'docker logs -f --since 2m zalo-caonguyen'

Expect: [Boot] Zalo listener started ✓ within 2s of container start, zero code 221 rate-limit lines.

Pre-prod checklist (self-verify before push):

  • npm run build passes with 0 TypeScript errors
  • DEV container ran new code ≥30 min without errors
  • No Unhandled rejection in DEV logs
  • Bridge 2-way works (Zalo→TG + TG→Zalo) on DEV
  • All commands respond (/login, /topic, /stopfollow, /follow)

Scenario 3 — Rollback PROD

When: Prod broken after release. Need to revert fast.

Method 1: Komodo UI (preferred)

  1. Open http://100.126.172.96:9120/builds → select zalo-tg
  2. Find last known-good Build version (check Created column)
  3. Note the :version-N or :commit-sha tag

Method 2: CLI (re-tag previous version)

# List recent image tags on Gitea registry
TOKEN="$GITEA_ACCESS_TOKEN"
curl -s -H "Authorization: token $TOKEN" \
  "http://100.68.251.84:3005/v2/lucndm/zalo-tg/tags/list" \
  | python3 -m json.tool

# Re-tag previous version as :latest, push, redeploy
docker pull 100.68.251.84:3005/lucndm/zalo-tg:<previous-version>
docker tag  100.68.251.84:3005/lucndm/zalo-tg:<previous-version> \
            100.68.251.84:3005/lucndm/zalo-tg:latest
docker push 100.68.251.84:3005/lucndm/zalo-tg:latest

# Force Komodo Stack to pull + recreate
km x -y stack zalo-tg

Method 3: Git revert + push

git revert <bad-commit-sha>
git push origin main
# Komodo Build auto-triggers on revert commit → new image

Scenario 4 — DEV container won't boot

Symptom: ./docker-local.sh up succeeds but container exits/crashes.

Triage

Boot log line Likely cause Fix
Missing required environment variable: TG_TOKEN .env missing or empty cp .env.example .env, fill TG_TOKEN + TG_GROUP_ID
Chưa có file credentials.json LucNguyen not logged in Send /login in DEV Telegram group, scan QR with LucNguyen Zalo app
TypeScript compile error Bad code docker run --rm -v "$PWD":/app -w /app node:20-alpine npm run build to see errors
EACCES on /app/data/* Volume permission mismatch docker volume rm zalo-tg_zalo-tg-data (destroys data!)
Container starts, immediate exit Check docker logs zalo-tg for stack trace Patch + rebuild

Reset DEV completely (lose history!)

./docker-local.sh down
docker volume rm zalo-tg_zalo-tg-data
./docker-local.sh up
# Then /login in DEV TG group to re-auth LucNguyen

Scenario 5 — PROD silent (no messages arriving)

Symptom: PROD Telegram group hasn't received Zalo forwards in >10 min, but Zalo app on phone shows new messages.

Triage in 4 steps

# Step 1: Container alive?
ssh root@100.68.251.84 'docker ps --filter name=zalo-caonguyen'

# Step 2: Recent logs
ssh root@100.68.251.84 'docker logs --since 5m zalo-caonguyen'

# Step 3: Session conflict? (multiple bridges on same account)
ssh root@100.68.251.84 'docker inspect zalo-caonguyen --format "{{.State.StartedAt}}"'
# Compare against local LucNguyen container — different Zalo accounts, should NOT conflict

# Step 4: Zalo API healthy? Try login flow
# Send /login in PROD TG group → bot should reply with QR image

Common causes

Symptom Cause Fix
Boot log shows Zalo listener started ✓ but no [Zalo→TG] lines Zalo server-side throttle (from past rate-limit storm) Wait 1-2h, or /login to refresh session
[Boot] Zalo bị ngắt do đăng nhập trùng phiên Same CaoNguyen logged in elsewhere Close Zalo Web/PC on other devices, then /login
[Boot] Zalo đã ngắt phiên bridge (kick connection) Zalo kicked the bridge session /login in PROD TG group
code 221 storm in logs Zalo API rate-limit Auto-handled by rate-limiter + negative cache (wait)
Image pull fails on unraid Gitea registry unreachable km x -y restart-stack gitea first, then km x -y stack zalo-tg

Scenario 6 — Sync upstream williamcachamwri/zalo-tg

When: Original project published new features/fixes you want to port.

Old archived Gitea repo (lucndm/zalo-tg on git.minhluc.info) serves as upstream tracker:

cd /workspaces/github.com/minhluc-info/zalo-tg        # old archived workspace
git fetch upstream                                     # williamcachamwri/zalo-tg
git log upstream/main --oneline -20                   # see what changed

# For each meaningful commit:
git show <sha>                                         # read diff
# Manually port to docker-compose/stacks/applications/zalo-tg/src/
# Test on DEV (Scenario 1), then promote (Scenario 2)

Upstream changes are usually small (project is mature). Manual porting keeps git history clean and avoids merge conflicts with local modifications (rate-limit fix, /stopfollow command, SQLite cache).


Daily operations

View PROD logs (last 5 min)

ssh root@100.68.251.84 'docker logs -f --since 5m zalo-caonguyen'

Force /login on PROD (credentials expired)

  1. Send /login command in PROD Telegram group
  2. Bot replies with QR image
  3. Open CaoNguyen Zalo app on phone → Settings → QR Login → Scan
  4. New credentials.json saved to /mnt/user/appdata/zalo-tg/caonguyen/credentials.json

Restart PROD without code change

km x -y restart-stack zalo-tg

Check Komodo Build status

km list builds -n zalo-tg -l        # last build + link
km list actions -n zalo-tg          # action history

Trigger Komodo Build manually (skip auto-trigger)

km x -y build zalo-tg

List image tags on Gitea registry

TOKEN="$GITEA_ACCESS_TOKEN"
curl -s -H "Authorization: token $TOKEN" \
  "http://100.68.251.84:3005/v2/lucndm/zalo-tg/tags/list"

Image tag strategy

Tag Purpose Lifetime
:latest Auto-update target for PROD Komodo Stack Rolling (overwritten each build)
:version-N Sequential build version (auto-increment) Permanent
:commit-sha Git commit hash at build time Permanent

Use :version-N or :commit-sha for rollback pinning. Never tag a rollback image as a new :version-N.


Key files reference

File Role
stacks/applications/zalo-tg/src/ TypeScript source (24 files)
stacks/applications/zalo-tg/Dockerfile Multi-stage Node 20 alpine build
stacks/applications/zalo-tg/compose.yaml PROD compose (CaoNguyen)
stacks/applications/zalo-tg/docker-compose.dev.yml DEV compose (LucNguyen, local-only)
stacks/applications/zalo-tg/.env DEV secrets — gitignored at repo root
stacks/applications/zalo-tg/.env.example DEV secrets template (committed)
stacks/applications/zalo-tg/docker-local.sh DEV helper script (build/up/down/logs)
komodo/builds/zalo-tg.toml Komodo Build resource config
komodo/stacks/applications/zalo-tg.toml Komodo Stack resource config (unraid deploy)