Troubleshooting
症状をVyline本体、永続化、Docker、Android hostに分けて確認します。
最初の確認
docker compose ps
docker compose logs --tail=200
docker inspect vyline
curl -fsS http://127.0.0.1:3000/healthz
df -hT
healthz が200ならVylineのHTTP server自体は動いています。ここから「保存だけ失敗」「containerだけ通信不可」「LANからだけ不可」のように、壊れている境界を1つに絞ってから直します。
復元履歴がreload後に消える
優先: /app/data bind mountとwrite permission。Protocolを疑う前に永続化を確認。
docker inspect vyline
docker logs vyline
du -sh data storage
解決方法
docker inspectのMountsで/app/dataと/app/storageが意図したhost directoryへbindされ、RW=trueになっていることを確認します。:ro、存在しないpath、移動前の古いpathを参照している場合はCompose/.envを修正します。既存データを移す場合はcontainerを止めてからコピーします。- 最新版imageでbind mountの初期化をやり直します。
docker compose pull
docker compose up -d --force-recreate
docker compose logs --tail=100
復元後にreloadし、さらにcontainerをrestartしても履歴が残ればSQLiteへの永続化まで成功しています。
容量が0 B
まず data / storage が意図した場所へmountされているか、ホスト側で容量を取得できるかを確認します。backendは保存先filesystemへ statfs を行うため、特殊filesystemや実行環境では容量取得に失敗することがあります。image更新を疑う場合は docker compose pull 後にrecreateして、同じ保存先で再確認します。
解決方法
df -hT data storage
docker exec vyline sh -lc 'df -hT /app/data /app/storage'
hostとcontainerで別filesystemが見えている場合はbind mount pathを修正します。host側でも容量取得が不安定なFUSE等の特殊filesystemなら、通常のext4/xfs/btrfs等へ保存先を移してbindし直します。pathを変える前に最新版imageへ更新して同じmountで再確認してください。
Raspberry Pi / 小型arm64ホストが極端に遅い
free -h、vmstat 1、docker stats でmemory pressureとswap in/outを確認します。zram/swapを増やすとOOMを避けられても、restoreや同期がI/O待ちで極端に遅くなることがあります。ストレージの遅延は iostat 等が使える環境なら併せて確認します。
解決方法
vmstatのsi/soが継続して増えるならswap thrashingです。zramを優先し、遅いSDカード上のswapへ常時逃がす構成を避けます。waが高い場合はdata//storage/をUSB SSD等の低遅延storageへ移します。- 大きなrestore中は他の重いcontainerを止めます。Portainer等の任意サービスも切り分け中は停止できます。
- swap増量はOOM回避であって高速化ではありません。memory pressureが解消しない場合は同時処理量を減らすか、RAMの多いhostへ移します。
Android: overlay2 EINVAL
docker info とmount情報から /var/lib/docker のfilesystemを確認します。F2FS/casefold等の組み合わせでoverlay2作成時に実際に EINVAL が出ている場合は、ext4 loop image上のdata-rootを回避策として検討します。
解決方法
F2FS/casefoldが原因と確認できた場合だけ、Android側root shellでDocker data-root用ext4 loopを作ります。サイズは空き容量に合わせて変更してください。
ROOT=/data/data/com.termux/files/usr/var/lib/proot-distro/containers/ubuntu/rootfs
mkdir -p /data/local/docker-storage "$ROOT/var/lib/docker"
truncate -s 16G /data/local/docker-storage/docker-ext4.img
/system/bin/mke2fs -t ext4 -F /data/local/docker-storage/docker-ext4.img
LOOP=$(/system/bin/losetup -f)
/system/bin/losetup "$LOOP" /data/local/docker-storage/docker-ext4.img
mount -t ext4 -o rw,noatime "$LOOP" "$ROOT/var/lib/docker"
chroot内でdockerdを起動し直し、docker info のBacking Filesystemと docker run --rm hello-world を確認します。元のfilesystemでoverlay2が正常ならこの回避は不要です。
Android: runc remount / invalid argument
chroot rootfsがself-bind mount/rslaveになっているか確認。
解決方法
PREFIX=/data/data/com.termux/files/usr
ROOT="$PREFIX/var/lib/proot-distro/containers/ubuntu/rootfs"
BB="$PREFIX/bin/busybox"
su
$BB mount --bind "$ROOT" "$ROOT"
$BB mount --make-rslave "$ROOT"
$BB mount --rbind /dev "$ROOT/dev"
$BB mount --make-rslave "$ROOT/dev"
$BB mount -t proc proc "$ROOT/proc"
$BB mount --rbind /sys "$ROOT/sys"
$BB mount --make-rslave "$ROOT/sys"
$BB mount -t tmpfs -o mode=755,nosuid,nodev tmpfs "$ROOT/run"
この順でmountしてからchrootへ入り直し、dockerdを再起動します。再起動後に docker run --rm hello-world まで通ることを確認します。boot後に再発する場合は、このmount処理をdockerdより前の自動起動手順へ入れます。
Android: containerからInternetへ出ない
ip rule、main route、FORWARD、MASQUERADE。Android netd policy routingを確認。
解決方法
まず ip_forward を有効化します。Androidの実packet pathがlegacy iptablesを通ることをcounterで確認した場合だけ、既存netd chainをflushせず専用chainを追加します。
echo 1 > /proc/sys/net/ipv4/ip_forward
WAN_IF=$(ip route get 1.1.1.1 | awk '/dev/ {for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}')
DOCKER_SUBNET=$(docker network inspect bridge --format '{{(index .IPAM.Config 0).Subnet}}')
iptables -N VYDOCKER_FWD 2>/dev/null || true
iptables -t nat -N VYDOCKER_POST 2>/dev/null || true
iptables -C FORWARD -j VYDOCKER_FWD 2>/dev/null || iptables -I FORWARD 1 -j VYDOCKER_FWD
iptables -t nat -C POSTROUTING -j VYDOCKER_POST 2>/dev/null || iptables -t nat -I POSTROUTING 1 -j VYDOCKER_POST
iptables -A VYDOCKER_FWD -i docker0 -o "$WAN_IF" -s "$DOCKER_SUBNET" -j ACCEPT
iptables -A VYDOCKER_FWD -i "$WAN_IF" -o docker0 -d "$DOCKER_SUBNET" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -t nat -A VYDOCKER_POST -s "$DOCKER_SUBNET" -o "$WAN_IF" -j MASQUERADE
Androidのpolicy routingがmain tableへ落ちない端末では、既存 ip rule と衝突しないpriorityにDocker subnetだけを lookup main へ送るruleを追加します。netd rule全体は削除しません。最後に docker run --rm alpine ping -c 1 1.1.1.1、続けてDNS/HTTPSを確認します。詳細は Android Networking。
Android: -p 3000:3000がLANからreset
hostでlistenしているか、Dockerが作ったNAT rule、Android側のiptables backend、実際にpacketが通るchainを順に確認します。検証済み端末ではnft系ruleとlegacy pathの不一致が原因になった例がありますが、確認せず固定DNAT ruleを追加しないでください。
解決方法
host自身の curl http://127.0.0.1:3000/healthz が通り、legacy pathをpacket counterで確認できた場合だけ、実際のLAN interface・container IP・bridge名から専用DNAT/FORWARD ruleを作ります。
CID=$(docker ps -qf 'name=vyline')
CONTAINER_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CID")
NETWORK_ID=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.NetworkID}}{{end}}' "$CID")
BRIDGE_IF="br-$(printf '%s' "$NETWORK_ID" | cut -c1-12)"
LAN_IF=wlan0 # ip -br addrで実値へ置き換える
iptables -t nat -N VYDOCKER_PRE 2>/dev/null || true
iptables -N VYDOCKER_FWD 2>/dev/null || true
iptables -t nat -C PREROUTING -j VYDOCKER_PRE 2>/dev/null || iptables -t nat -I PREROUTING 1 -j VYDOCKER_PRE
iptables -C FORWARD -j VYDOCKER_FWD 2>/dev/null || iptables -I FORWARD 1 -j VYDOCKER_FWD
iptables -t nat -A VYDOCKER_PRE -i "$LAN_IF" -p tcp --dport 3000 -j DNAT --to-destination "$CONTAINER_IP:3000"
iptables -A VYDOCKER_FWD -i "$LAN_IF" -o "$BRIDGE_IF" -p tcp -d "$CONTAINER_IP" --dport 3000 -j ACCEPT
Compose recreateでcontainer IPやbridge名が変わるため、この回避が必要な端末ではboot・Docker network変更時に専用chainを再生成します。Wi-Fi以外では wlan0 を固定しません。
Android: docker execで/appが無い
PID=$(docker inspect -f '{{.State.Pid}}' vyline)
readlink /proc/$PID/ns/mnt
ls -la /proc/$PID/root/app
nsenter -t "$PID" -m -- ls -la /app
docker exec vyline ls -la /app
コンテナPID rootが正常なら docker exec のnamespaceだけ壊れている可能性があります。
解決方法
/proc/$PID/root と nsenter では正常で、docker exec だけ異常ならimageやvolumeを削除しません。real chrootのmountをself-bind + rslaveで作り直し、そのmount namespace内でdockerdを再起動してcontainerをrecreateします。
本体HTTPが正常でhealthcheckだけ同じexec経路で失敗する場合は、原因を確認したうえでComposeのhealthcheckを無効化する回避も可能です。診断中は nsenter でcontainerの実mount viewを確認します。
リモートアクセスで401 / 403になる
非loopback bindでは VYLINE_LAN_ACCESS=false でもremote authenticationが必要です。subdeviceのpairing状態とinstallation IDを確認してください。owner専用操作をremote browserから使うために VYLINE_TRUST_REMOTE_OWNER=true を設定するのは、Cloudflare AccessやTailscale ACL等で到達経路自体を認証済みにしている場合だけです。
解決方法
- 通常のLAN利用では
VYLINE_TRUST_REMOTE_OWNER=falseのまま、loopback側owner画面からsubdeviceをpairingし直します。 - browser dataを消した・別browserへ移った場合はinstallation IDが変わるため再pairingします。
- Cloudflare Access、Tailscale ACL、認証済みreverse proxy等でbackendへ到達する前に利用者を制限しており、remote browserをowner扱いする必要がある場合だけ
VYLINE_TRUST_REMOTE_OWNER=trueを設定してrecreateします。
docker compose up -d --force-recreate
docker compose logs --tail=100
生のLAN/Internetへ公開した状態で VYLINE_TRUST_REMOTE_OWNER=true にするのは解決方法ではありません。認証境界を先に用意してください。