Vyline DOCS

Android 完全構築ガイド

root済みarm64 AndroidをDockerホストとして使う場合の構成と切り分け手順です。

通常のAndroid向けインストールではありません

VylineにはAndroidネイティブ版Dockerホスト機能はありません。このページは、root権限とcontainer向けkernel機能を持つAndroid端末をLinuxホストとして使う特殊構成を扱います。普通のLinuxサーバーやRaspberry Piでは、この手順は不要です。

適用範囲

WebサイトのAndroid手順は、次の2種類の情報を分けています。

区分内容
共通要件arm64、root、namespaces/cgroups/seccomp、Dockerが使うfilesystem/network機能、real chroot、永続化
条件付き回避策F2FS上のoverlay2失敗時のext4 loop、アクセス制御の原因切り分け、Android netd/policy routing補正、legacy iptables DNAT等

後者は端末・ROM・kernel・Android versionで変わります。症状が出ていない環境へ一律に適用しないでください。

このガイドの考え方

AndroidをDockerホストにするときは、機種名ではなく実際のkernel機能、filesystem、mount propagation、cgroup構成、netfilter backend、Androidのrouting ruleを見て判断します。同じSoCや同じAndroid versionでも、vendor kernel・custom kernel・ROMによって結果が変わるためです。

以下の回避策は実機で再現した失敗と復旧方法を一般化したものです。症状が一致した場合だけ適用し、正常に動いている層へ不要な変更を入れないのが基本です。

構成

Android arm64
├─ Android OS / vendor kernel
│  ├─ root
│  ├─ namespaces / cgroups / seccomp
│  ├─ OverlayFS / ext4 / loop
│  └─ veth / bridge / netfilter
├─ Termux
│  └─ rootfs取得・SSH・起動入口
├─ Linux rootfs
│  └─ real chroot
├─ Docker Engine / containerd / runc
└─ Vyline
   ├─ /app/data
   └─ /app/storage

proot-distro はUbuntu rootfsの取得には便利ですが、Docker daemonを常用する層にはしません。Docker/runcはkernelのnamespace、mount、cgroup、deviceを直接使うため、実行時はrootでreal chrootを使います。

1. Preflight

uname -a
uname -m
id
getenforce
cat /proc/cgroups
mount | grep -E 'cgroup|cgroup2'
ls -l /dev/block/loop* 2>/dev/null

uname -maarch64 を想定します。root shellが取れない端末では、この構成は成立しません。

kernel側は少なくともnamespace、cgroup、seccomp、OverlayFS、veth、bridge、netfilter/conntrack/NAT、ext4、loop deviceを確認します。具体的なCONFIGの読み方は Android Kernel Requirements に分離しています。

最初に端末を4分類する

状態判断次にすること
docker run --rm hello-world まで通るcontainer runtimeの基礎は成立networkと永続化へ進む
dockerdが起動しないkernel/cgroup/seccomp/storageの問題dockerd --debug とkernel configを確認
containerは起動するが外へ出られないAndroid routing/netfilterの問題ip rule / FORWARD / MASQUERADEを確認
外へ出られるが -p がLANから通らないpublished-portのpacket path不一致nft/legacy、DNAT、bridge FORWARDを確認

2. Termuxとrootfs

pkg update -y
pkg install -y proot-distro busybox openssh tmux coreutils curl
proot-distro install ubuntu

標準的なrootfs位置はTermuxの var/lib/proot-distro/containers/ubuntu/rootfs 以下です。以後、実際のパスを ROOT として扱います。

PREFIX=/data/data/com.termux/files/usr
ROOT="$PREFIX/var/lib/proot-distro/containers/ubuntu/rootfs"
BB="$PREFIX/bin/busybox"

proot-distro login ubuntu はrootfsの初期設定には使えますが、dockerd/runcの常用経路にはしません。prootはsyscallをユーザーランドで変換するため、Dockerが必要とするmount namespace、cgroup、device、propagationをそのまま扱えません。

3. real chroot用のmount

単純に chroot "$ROOT" するだけでruncが動くとは限りません。一部のAndroid kernel / mount構成ではrootfsをmountpoint化し、子mountをhostへ伝播させないようrslaveにする必要があります。

$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"

remount / ... invalid argument のようなruncエラーが出る場合は、最初にこのmount構成を確認します。問題が出ていない端末では、不必要なremountを増やさないでください。

/dev/pts とDNS

/dev--rbindしていれば通常は/dev/ptsも見えます。端末によってはptyやDNSだけ不完全になるため、chroot内で次を確認します。

ls -ld /dev/pts /proc /sys /run
cat /etc/resolv.conf
getent hosts download.docker.com
mount | grep -E "$ROOT|/var/lib/docker"

DNSだけ失敗する場合は、Android側で実際に使えるresolverを$ROOT/etc/resolv.confへ用意します。固定のpublic DNSを常用設定として決め打ちせず、Wi-FiやVPNを切り替えた後にも名前解決できるか確認してください。

4. SELinux

SELinux enforcingのままDockerを動かせるなら、そのまま使います。loop device、mount、dockerd、iptables操作が拒否される場合はaudit logを確認して原因を切り分けます。

setenforce 0 は一般要件ではありません

permissiveで症状が消えるならSELinuxが原因候補だと分かりますが、Android全体の防御を弱めます。切り分け後はaudit logから拒否対象を確認し、可能なら必要なsepolicyだけを追加してください。

5. Docker data-root

まず現在のfilesystem上でDockerのstorage driverが正常に動くか確認します。Androidの /data がF2FS/casefold等の構成で overlay2 作成時に EINVAL となる場合にだけ、ext4 loop imageへ逃がします。

findmnt -T /var/lib/docker 2>/dev/null || true
stat -f -c '%T' /data
docker info 2>/dev/null | grep -E 'Storage Driver|Backing Filesystem|Supports d_type'

F2FSだから必ず失敗するわけではありません。実際にoverlay2のlayer作成やcontainer起動でinvalid argument/EINVALが出た場合にfallbackします。

ext4 loopへ切り替える例:

mkdir -p /data/local/docker-storage
truncate -s 64G /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
$BB mount -t ext4 -o rw,noatime "$LOOP" "$ROOT/var/lib/docker"

64GBは例です。端末の空き容量とVylineの保存メディア量に合わせて決めてください。F2FS上でoverlay2が正常なら、このloop構成は必須ではありません。

loop imageはAndroid userdata上の単一ファイルです。端末の空き容量を超える設定、突然の電源断、loop番号の固定決め打ちは避けます。再起動時はlosetup -fで空きを取り直し、既にattach済みなら二重attachしないよう判定します。

6. Docker Engine

Ubuntu rootfs内では、通常のUbuntu向けDocker公式repositoryからEngine、containerd、Compose pluginを導入します。systemdがPID 1ではない構成では、systemctl start docker ではなくdockerdを直接起動します。

apt update
apt install -y ca-certificates curl gnupg iproute2 iptables procps kmod util-linux coreutils
apt update
apt install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
. /etc/os-release
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu ${UBUNTU_CODENAME:-$VERSION_CODENAME} stable" \
  > /etc/apt/sources.list.d/docker.list
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

systemdを前提にしないdaemon設定

Android initがPID 1のreal chrootでは、Ubuntu側systemdをDocker service managerとして使わない構成が単純です。cgroup mountを確認し、systemd driverが成立しない場合はcgroupfsを明示します。

mkdir -p /etc/docker
cat >/etc/docker/daemon.json <<'EOF'
{
  "storage-driver": "overlay2",
  "exec-opts": ["native.cgroupdriver=cgroupfs"],
  "log-driver": "local"
}
EOF

既に別のdaemon.jsonがある場合は上書きせず内容をmergeしてください。cgroup v2が正しく公開され、別driverで安定している端末へ無理にcgroupfsを強制する必要はありません。

{
  "storage-driver": "overlay2",
  "exec-opts": ["native.cgroupdriver=cgroupfs"],
  "log-driver": "local"
}

上記はAndroid chrootのリファレンス設定です。cgroup構成が異なる端末では、daemon logを見て調整します。

rm -f /var/run/docker.pid
nohup dockerd >/var/log/dockerd.log 2>&1 </dev/null &

docker version
docker info
docker run --rm hello-world
docker run --rm alpine uname -m

docker infoではStorage Driver、Backing Filesystem、Cgroup Driver/Version、Architectureを見ます。arm64 hostでArchitecture: aarch64、選択したstorage/cgroup構成になっていることを確認します。

hello-world が通らない状態でVylineへ進まないでください。ここで落ちる問題はVylineではなくAndroid host / Docker側です。

dockerdのlogで見る場所

tail -n 200 /var/log/dockerd.log
docker info
cat /proc/self/cgroup
mount | grep -E 'cgroup|overlay|/var/lib/docker'

failed to mount overlayならstorage、cgroup/devices/pidsならkernel/cgroup、failed to create NAT chainならnetfilter/iptablesを優先して切り分けます。

7. Docker network

次にcontainerから外へ出られるか、host publishが通るかを分けて確認します。

docker run --rm alpine ping -c 1 1.1.1.1
docker run --rm -p 8080:80 nginx:alpine

通常Linuxと同様に通れば追加作業は不要です。失敗するときだけAndroidの netd、policy routing、ip_forward、FORWARD、MASQUERADE、iptables backendを調べます。

一部のAndroidでは、chroot内Dockerがnft系ruleを作る一方、実packet pathはAndroid側legacy iptablesを通り、published portだけ機能しないことがあります。この場合はDNAT/FORWARDをAndroid側の実経路へ補います。interface名、routing table、iptables backend、chain名は端末ごとに異なるため、固定値をコピーせず Android Docker Networking の診断順で確認してください。

ip -br addr
ip rule
ip route show table main
ip route get 1.1.1.1
cat /proc/sys/net/ipv4/ip_forward
iptables --version
iptables -S FORWARD
iptables -t nat -S
docker network ls
docker network inspect bridge

8. Vylineを起動

Docker自体が正常になった後は、通常のarm64 Linuxホストと同じComposeを使えます。GHCR imageは linux/arm64 を提供しています。

mkdir -p /opt/vyline
cd /opt/vyline
curl -LO https://raw.githubusercontent.com/tqmane/vyline/main/docker-compose.yml
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100

Composeは ./data/app/data./storage/app/storage へmountします。更新時にこの2つを消さないでください。

curl http://127.0.0.1:3000/healthz

LANから開く場合、Composeのhost portは既定で 0.0.0.0:3000 です。ただし VYLINE_LAN_ACCESS=false のままでも、非loopback配置ではremote requestにsubdevice認証が要求されます。設定の意味は Access Model を参照してください。

永続化を先に確認する

mkdir -p /opt/vyline/data /opt/vyline/storage
touch /opt/vyline/data/.write-test /opt/vyline/storage/.write-test
docker inspect vyline --format '{{json .Mounts}}'
df -h /opt/vyline/data /opt/vyline/storage

ログイン、復元、メディア保存まで進んだあとにmountミスへ気付くと状態を失います。container再作成後も同じhost pathがmountされることを先に確認してください。

9. Portainerは任意

Vylineの起動にPortainerは必要ありません。Docker CLI / Composeで安定した後に管理UIとして追加します。Android chrootでbridge publishが壊れる環境では、Portainerだけhost networkを使う回避策が有効な場合がありますが、通常Linuxの既定手順ではありません。

Cloudflare Tunnel、Tailscale、WireGuardも同じく任意です。LAN内だけで使うなら不要です。外部アクセスが必要な場合だけ、到達経路を認証するための選択肢として追加します。

10. 自動起動

自動起動する場合は、順序を固定します。

1. root取得
2. chroot rootfsのmount
3. 必要ならext4 loopをattach/mount
4. 必要ならAndroid network補正
5. dockerd起動
6. docker infoでready確認
7. docker compose up -d

Androidのboot直後はdata mountやnetwork interfaceが未確定なことがあります。固定秒数だけ待つより、必要なmount/interface/docker socketが現れたことを確認して次へ進む方が壊れにくくなります。

Termux:Boot等を使う場合も「起動したら即dockerd」ではなく、/data、rootfs、loop mount、network interfaceの準備完了を条件にします。Dozeで長時間停止する端末では、サーバー専用運用に限ってTermuxのbattery optimization除外やwake lockを検討します。

11. 症状別の切り分け

症状最初に見る場所
overlay2 ... invalid argumentDocker data-rootのfilesystem。F2FS/casefoldならext4 loopを検討
remount / ... invalid argumentchroot rootfsのself-bind / mount propagation
containerからInternetへ出ないip rule、route、FORWARD、MASQUERADE、netd
-p 3000:3000 がLANからresetDockerが使うiptables backendとAndroid側packet path
docker exec だけfilesystemがおかしい/proc/<container-pid>/root とexec namespaceを比較
Vylineのreload後に復元状態が消える/app/data / /app/storage のmountとwrite permission

docker execだけ壊れる場合

一部のAndroid + real-chrootでは、container本体は正常なのにexecで入ったprocessだけ異なるmount viewを掴むことがあります。docker execだけを証拠に「containerの/appが消えた」と判断しません。

CID=$(docker inspect -f '{{.State.Pid}}' vyline)
readlink /proc/$CID/ns/mnt
ls -la /proc/$CID/root/app
nsenter -t "$CID" -m -- ls -la /app
docker exec vyline ls -la /app

/proc/$PID/rootnsenterでは正常で、docker execだけ異常ならruntime/chrootのexec経路を疑います。同じ理由でhealthcheckだけ失敗するホストでは、HTTPをhost側から確認したうえでCompose側healthcheckを無効化する選択肢があります。

Vyline固有の症状は Troubleshooting、Android kernelは Kernel Requirements、networkは Android Networking へ進んでください。

12. セキュリティ

必須・条件付き・任意

要素扱い
arm64 / root / container向けkernel機能必須
real chrootこの構成では必須
GHCRのarm64 Vyline image通常はこれを使用
ext4 loop imageF2FS等でoverlay2が失敗するときだけ
SELinux permissive原因切り分け用の条件付き回避策。一般要件ではない
手動route / iptables補正Android networkで実際に失敗するときだけ
Portainer任意
Cloudflare Tunnel / Tailscale外部アクセスが必要な場合のみ
KVMVylineには不要
ページ名・設定名・エラー名で検索