ARM から x86 へ:VPS サーバー移行で持っていけるもの、作り直すもの
ARM の VPS で動くサーバーを x86 へ移す手順。データや設定、マルチアーキテクチャの Docker イメージはそのまま、実行ファイルや venv は作り直し、スナップショットは諦める。uname -m や file、docker manifest inspect で仕分け、手戻りのない順番で切り替える。
まず結論:ARM から x86 へ移すとき、持っていけるのはデータと設定です
ARM の VPS で動いているサーバーを x86 の VPS へ移すとき、そのまま持っていけるのはデータ、設定ファイル、スクリプトやソースコード、そして複数のアーキテクチャ向けに公開されている Docker イメージです。自分でコンパイルした実行ファイル、ネイティブ拡張を含む Python の venv や node_modules、手元でビルドした Docker イメージは、移行先で作り直します。プロバイダのスナップショット、単一アーキテクチャのコンテナイメージ、カーネルモジュール、各種のビルドキャッシュは持っていけません。
仕分けの基準は一つだけです。「その中に、特定の CPU の機械語に変換されたものが入っているか」。入っていなければ持っていけます。入っていれば作り直すか、代わりを探します。買う前にどちらのアーキテクチャを選ぶかは ARM VPS と x86 VPS の違い で扱っています。以下で扱うのは、すでに片方で動いているサーバーを、もう片方で動かすまでの作業です。
なぜ日本では ARM から x86 への移動が多いのか
2026 年 9 月時点で、国内の VPS 事業者が提供するプランは x86 が圧倒的多数です。SSD Nodes のプランも x86 です。一方で、読者の多くが ARM のサーバーに触れた場所は、Oracle Cloud の Always Free 枠にある Ampere A1、AWS の Graviton、あるいは自宅の Raspberry Pi や Apple シリコンの Mac です。無料枠や検証用に ARM で育てた構成を月額固定の国内 VPS へ引っ越すとなると、向きは自然と ARM から x86 になります。
逆向き、x86 から ARM への移動も手順は同じです。違うのは山の大きさだけで、ARM 向けにはまだ単一アーキテクチャのイメージや、ビルド済み wheel の無い Python パッケージが残っているので、「作り直す」山が大きくなります。どちらの向きでも、最初にやるのは両方の箱でアーキテクチャの呼び名を揃えることです。
アーキテクチャの呼び名を揃える
同じものが道具ごとに違う名前で呼ばれます。棚卸しの前に対応を頭に入れておくと、出力を見て迷いません。
uname -mはx86_64かaarch64を返します。dpkg --print-architectureはamd64かarm64を返します。apt はこの名前でパッケージを選びます。- Docker は
linux/amd64とlinux/arm64/v8(省略してlinux/arm64)を使います。 - Go は
GOARCH=amd64とGOARCH=arm64です。 - Python の wheel のファイル名には
x86_64かaarch64が入ります。 - Apple シリコンの Mac は
arm64で、サーバーのaarch64と同じ命令セットです。
amd64 は AMD 製 CPU 専用という意味ではありません。Intel の CPU でも amd64 です。64 ビットの x86 を最初に出したのが AMD だった、という由来の名前です。
移行元と移行先の両方で確認します。
uname -m
dpkg --print-architecture移行元で aarch64 と arm64、移行先で x86_64 と amd64 が出れば、以下の手順の向きです。両方で同じ値が出るなら、アーキテクチャの問題はなく、通常のサーバー移行の手順 だけで済みます。
箱の中身を三つの山に分ける
移行元にログインして、動いているものを全部書き出します。
systemctl list-units --type=service --state=running --no-pager
docker ps
ls -la /srv /opt /usr/local/bin ~/.local/bin ~/go/bin ~/.cargo/bin 2>/dev/null
crontab -l書き出した一つ一つを、次の三つの山に入れていきます。判断に迷ったら file に聞きます。file はファイルの中身の先頭を読んで種類を答えるので、拡張子や置き場所に関係なく、機械語が入っているかどうかを教えてくれます。
file /usr/local/bin/*ELF 64-bit LSB pie executable, ARM aarch64 と出た行は、移行先では動きません。POSIX shell script や Python script と出た行は、そのまま持っていけます。
山1:そのまま持っていけるもの
- アプリケーションのデータ。アップロードされたファイル、ログ、メディア。
- 設定ファイル。
/etc以下、~/.bashrcなどのドットファイル、.env。 - systemd のユニットファイルと crontab。ただし
ExecStartが指す実行ファイルは山2で確認します。 - TLS 証明書と秘密鍵。
/etc/letsencrypt以下は PEM のテキストファイルです。live/の中はシンボリックリンクなので、rsync -aかtarで運びます。 - SSH の鍵と
authorized_keys。 - シェルスクリプトと、Python、Node.js、PHP、Ruby のソースコード。
- SQLite のデータベースファイル。SQLite のファイル形式はアーキテクチャに依存しないと公式に定められています。
docker-compose.ymlと、そこから参照される複数アーキテクチャ対応のイメージ。
最後の一つだけ、確認が要ります。レジストリにある一つのタグは、実際には「マニフェストリスト」(OCI の用語では「イメージインデックス」)という、アーキテクチャごとのイメージへの目次になっていることが多いです。docker pull は目次から自分のアーキテクチャに合うものを選んで落とします。目次に linux/amd64 が載っていれば、同じ Compose ファイルが x86 でそのまま動きます。
Compose ファイルが参照するイメージを全部並べて、それぞれの目次を見ます。
cd /srv/app
docker compose config --images
for img in $(docker compose config --images); do
echo "== $img"
docker buildx imagetools inspect "$img" | grep Platform
done複数アーキテクチャ対応のイメージなら、Platform: linux/amd64 と Platform: linux/arm64/v8 の行が並びます。Platform: unknown/unknown の行は署名や SBOM を入れるためのもので、無視して構いません。Platform の行が一つも出ないイメージは、目次ではなく単一のイメージが直接置かれているので、山3の候補です。build: で手元からビルドしているサービスはレジストリに存在しないのでエラーになりますが、それは正常で、山2に入れます。
docker buildx が無い環境(Ubuntu 標準の docker.io パッケージ)では、sudo apt install docker-buildx で追加するか、docker manifest inspect で代用します。
docker manifest inspect nginx:latest | grep architecture"architecture": "amd64" の行があれば x86 で動きます。単一アーキテクチャのイメージでは目次が無いので、architecture の行は一行も出ません。
もう一つ、見落としやすい落とし穴があります。image: nginx@sha256:... のようにダイジェストで固定している場合、そのダイジェストが目次を指しているのか、ARM 向けのイメージ一つを指しているのかで結果が変わります。ARM の箱で docker image inspect から拾ったダイジェストは後者であることが多く、x86 で pull すると ARM のイメージがそのまま落ちてきます。ダイジェストのまま docker buildx imagetools inspect に渡して、Manifests: の一覧が出るかどうかで見分けます。
山2:移行先で作り直すもの
この山に入るのは「ソースはあるが、ビルドの結果が ARM 向け」というものです。作り直しは移行先で行い、移行元のビルド成果物はコピーしません。
自分でビルドした実行ファイル。 Go、Rust、C、C++ で書いたものが /usr/local/bin や /opt に置かれていれば、file が ARM aarch64 と答えます。Rust と C は移行先で cargo build --release や make をやり直すのが確実です。Go はクロスコンパイルが簡単なので、ARM の箱から x86 向けを直接作れます。
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app-amd64 .
file app-amd64file が ELF 64-bit LSB executable, x86-64 と答えれば、そのファイルを移行先に置くだけで動きます。CGO_ENABLED=0 を付けるのは、C のライブラリに依存しない静的な実行ファイルにするためです。cgo を使うパッケージが混ざっていると x86 向けの C コンパイラが別に必要になり、付けておけば移行先の glibc のバージョンにも依存しません。
Python の venv。 venv のディレクトリはコピーしません。numpy、cryptography、psycopg2-binary、Pillow のようなパッケージは C で書かれた拡張を .so ファイルとして含んでいて、そのファイル名にはアーキテクチャが入っています。
find /srv/app/.venv -name '*.so' | head -n 3_multiarray_umath.cpython-312-aarch64-linux-gnu.so のような名前が出ます。x86 の Python は x86_64-linux-gnu の付いたファイルしか拡張モジュールとして読まないので、コピーした venv では import がモジュールを見つけられません。移行元で依存の一覧だけを取り出し、移行先で作り直します。
# 移行元
/srv/app/.venv/bin/pip freeze > requirements.txt
# 移行先
python3 -m venv /srv/app/.venv
/srv/app/.venv/bin/pip install -r requirements.txtNode.js の node_modules。 同じ話です。sharp、bcrypt、better-sqlite3、argon2 のようなパッケージは、.node という拡張子のネイティブ拡張を持ちます。
find /srv/app/node_modules -name '*.node' | head -n 3一つでも出れば、node_modules はコピーせず、移行先で npm ci を実行して package-lock.json から作り直します。.node が一つも無くても、コピーではなく npm ci で作った方が、後で悩む時間が減ります。
手元でビルドした Docker イメージ。 Dockerfile から docker build や docker compose build で作ったイメージは、ビルドした箱のアーキテクチャになります。日本で特に多いのが、Apple シリコンの Mac で docker build して push したイメージを x86 の VPS で pull する事故です。Docker Desktop はホストに合わせて linux/arm64 でビルドするので、そのイメージは x86 では起動しません。
移行先で docker compose build をやり直すのが最も単純です。Mac から x86 向けを作りたいなら、--platform を明示します。
docker buildx build --platform linux/amd64 -t registry.example.com/app:1.0 --push .Compose ファイルのサービスに platform: linux/amd64 を書いておけば、docker compose build も docker compose pull も、どの箱で実行してもそのアーキテクチャを使います。
curl | sh 形式のインストーラで入れたもの。 rustup、nvm 経由の Node.js、uv、Bun などは、インストーラが実行時にアーキテクチャを判定して合う実行ファイルを落とします。~/.cargo や ~/.nvm を丸ごとコピーせず、移行先でインストーラをもう一度実行します。
山3:持っていけないもの
プロバイダのスナップショットと VM イメージ。 スナップショットはディスクの丸ごとのコピーで、中には ARM 向けのカーネル、ブートローダ、共有ライブラリ、全パッケージが入っています。x86 の VPS に持ち込んで起動できる要素が一つもありません。そもそも多くのプロバイダは自社内でしかスナップショットを復元させないので、アーキテクチャ以前に プロバイダをまたぐ可搬性 の問題でもあります。スナップショットの代わりになるのは、OS のクリーンな状態から構成を再現できる手順です。Compose ファイルと cloud-init による初期設定 があれば、そのまま x86 の新しい VPS の初期化にも使えます。
単一アーキテクチャのコンテナイメージ。 山1の確認で Platform が linux/arm64 の一行しか出なかった、あるいは一行も出なかったイメージです。選択肢は三つで、同じソフトウェアの別タグや別の配布元(-amd64 の付いたタグや、公式が用意した別のリポジトリ)を探す、公開されている Dockerfile から移行先でビルドする、最後の手段としてエミュレーションで動かす、です。エミュレーションは後述しますが、常用するものではありません。
カーネルモジュールと、それに依存するソフトウェア。 .ko ファイルは特定のカーネルと特定のアーキテクチャ向けにビルドされているので、決して持っていけません。移行元で何が入っているかを見ます。
command -v dkms && dkms status
lsmod | head -n 20dkms status の出力には zfs/2.2.2, 6.8.0-45-generic, aarch64: installed のように、アーキテクチャがそのまま書かれています。DKMS で入れたものは、移行先で同じパッケージを apt から入れ直せば、x86 のカーネルに対して自動でビルドし直されます。プロバイダの監視エージェントや商用の VPN クライアントなど、バイナリで配布されるモジュールは、x86 版を配布元から取り直します。WireGuard はカーネル本体に入っているので、この山には入りません。
ビルドキャッシュとパッケージキャッシュ。 ~/.cache/pip、~/.npm、~/.cache/go-build、/var/cache/apt はすべてアーキテクチャ固有の成果物です。コピーする理由がなく、コピーすると移行先で古い ARM 向けの成果物が再利用される事故の元になります。
手戻りのない順番:棚卸し、作り直し、データ、切り替え
順番を間違えると、切り替えた後に「動かない」が見つかります。データを最後に、切り替えをさらにその後に置くのは、移行先が本当に動くと確認できるまで移行元を止めないためです。
- 棚卸し。上の三つの山に全部を仕分けし、山3で代わりが見つからないものがあれば、ここで移行そのものを考え直します。ソフトウェアが VPS で動くかどうかの一般的な判断は VPS で動くかどうかの互換性チェック にまとめてあります。
- 移行先の x86 VPS を先に組む。OS を入れ、Docker を導入 し、山2のものを全部作り直し、山1のイメージを pull します。
- 移行先で、テスト用のデータか移行元のデータのコピーで一度動かします。
docker compose psで全サービスがrunningかhealthyになり、curlで応答するまで確認します。 - データを移す。ファイルは
rsync、データベースは論理ダンプで移します。 - 切り替える。DNS の TTL を下げ、移行元のサービスを止め、最後の差分を同期し、DNS を向け直します。
手順 2 と 3 が終わるまで、移行元を止めたり書き換えたりしません。ここで動かないものが出ても、失うのは移行先で使った時間だけです。
データの移し方
ファイルは rsync です。所有者の UID と GID を数値のまま運ぶ --numeric-ids と、ACL と拡張属性を運ぶ -AX を付けます。
sudo rsync -aHAX --numeric-ids --info=progress2 \
--rsync-path='sudo rsync' \
/srv/app/data/ ubuntu@203.0.113.20:/srv/app/data/--rsync-path='sudo rsync' は、移行先に ubuntu ユーザーとしてログインしつつ、書き込みだけ root で行うための指定です。Ubuntu のクラウドイメージでは ubuntu ユーザーがパスワード無しで sudo を使えるので、そのまま動きます。移行先で同じ名前のユーザーが違う UID で作られていると、コピーしたファイルの所有者が別人になるので、id で両方の UID を先に見比べておきます。
データベースは、データディレクトリの丸ごとコピーではなく論理ダンプを使います。PostgreSQL と MySQL のデータディレクトリをそのまま運ぶ操作は、同じ OS、同じアーキテクチャ、同じメジャーバージョン、同じビルド設定を前提にしています。アーキテクチャをまたぐと前提の一つが崩れるので、動くかどうかを調べる代わりに、どの組み合わせでも通るダンプを使います。
# 移行元(Compose の db サービスが PostgreSQL の場合)
docker compose exec -T db pg_dump -U app -Fc app > app.dump
# 移行先
docker compose exec -T db pg_restore -U app -d app --clean --if-exists < app.dumpMySQL と MariaDB なら mysqldump --single-transaction と mysql の組み合わせで同じことをします。SQLite だけは例外で、アプリを止めた状態でファイルをコピーすれば終わりです。
切り替え
DNS の TTL を下げる時期、最終同期の手順、旧サーバーを残しておく期間、逆戻りの手順は、アーキテクチャに関係なく同じです。サーバー移行の切り替え手順 にまとめてあるので、ここでは繰り返しません。アーキテクチャをまたぐ移行で一つだけ足すなら、切り替え後も移行元の ARM 側を「戻り先」として最低一週間は残しておくことです。作り直した山2のどれかに見落としがあった場合、戻り先があれば数分で元に戻せます。
エミュレーションは橋であって、住む場所ではない
x86 の Docker に QEMU を登録すれば、ARM 向けのイメージをそのまま動かせます。
docker run --privileged --rm tonistiigi/binfmt --install all
docker run --rm --platform linux/arm64 alpine uname -m二つ目のコマンドが aarch64 と返せば、エミュレーションが動いています。ただし ARM の機械語を実行のたびに x86 の機械語へ変換するので、一般に処理は数倍から十倍以上遅くなり、CPU 時間をそのぶん消費します。山3の単一アーキテクチャイメージに代わりが見つかるまでの一時しのぎ、あるいは代わりが見つかるかどうかを移行先で試すための道具と考えてください。
つまずいたときに見える文字列
cannot execute binary file: Exec format error。 ARM 向けの実行ファイルを x86 で起動したときの、カーネルからの返事です。file で確かめれば ARM aarch64 と出ます。山2に戻して作り直します。
no matching manifest for linux/amd64 in the manifest list entries。 docker pull が、目次はあるのに linux/amd64 の項目が無いイメージを引いたときの返事です。そのイメージは ARM 向けにしか公開されていないので、山3です。
WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v3) に続く exec /docker-entrypoint.sh: exec format error。 pull は通ったが、落ちてきたのが単一アーキテクチャの ARM イメージだった場合です(v3 の部分は CPU 世代で変わります)。ダイジェスト固定か、Mac でビルドして push したイメージが原因のほとんどを占めます。docker image inspect <image> | grep Architecture で arm64 と出れば確定です。
ModuleNotFoundError や ImportError が、pip list には載っているパッケージで出る。 venv をコピーした場合の典型です。.so のファイル名に aarch64 が入っていることを find で確かめ、venv を作り直します。
ボリュームの中のファイルの所有者が 1001 のような数字で表示される。 --numeric-ids で運んだ UID が、移行先に存在しないユーザーの番号だった場合です。コンテナの中のプロセスがその UID で動くなら実害はありません。ホスト側のユーザーが読む必要があるなら、移行先で同じ UID のユーザーを作るか、chown で揃えます。
FAQ
ARM の Mac でビルドした Docker イメージは x86 の VPS で動きますか?
そのままでは動きません。Apple シリコンの Docker Desktop はホストに合わせて linux/arm64 のイメージを作るので、x86 の VPS で pull すると exec format error で起動に失敗します。docker buildx build --platform linux/amd64 で x86 向けにビルドするか、Compose ファイルのサービスに platform: linux/amd64 を書いてから push し直します。VPS 側で Dockerfile からビルドし直すのが、最も間違いの少ない方法です。
データベースのデータディレクトリを rsync でそのまま移してもいいですか?
SQLite は構いません。ファイル形式がアーキテクチャに依存しないと公式に定められているので、アプリを止めてコピーすれば終わりです。PostgreSQL と MySQL のデータディレクトリをそのまま運ぶ操作は、同じアーキテクチャと同じバージョンを前提にしているので、アーキテクチャをまたぐ移行では pg_dump -Fc と pg_restore、または mysqldump --single-transaction と mysql で論理ダンプを取り、移行先で復元します。
プロバイダのスナップショットを x86 の VPS に持ち込めますか?
できません。ARM の VPS のスナップショットには ARM 向けのカーネル、ブートローダ、共有ライブラリ、全パッケージが入っていて、x86 の CPU では一つも実行できません。加えて、ほとんどのプロバイダはスナップショットを自社内でしか復元させません。代わりに、OS のクリーンな状態から構成を再現できるもの、つまり Compose ファイル、設定ファイルのバックアップ、cloud-init の設定、手順書を持ち、それを x86 の新しい VPS に流し込みます。
x86 から ARM への逆方向も同じ手順ですか?
同じです。三つの山の基準は「特定の CPU の機械語が入っているか」なので、向きには依存しません。違うのは山の大きさで、ARM 向けにはまだ単一アーキテクチャのイメージや、ビルド済み wheel の無い Python パッケージが残っているため、「作り直す」山と「持っていけない」山が大きくなります。docker buildx imagetools inspect で Platform: linux/arm64/v8 の行があるか、pip download --only-binary=:all: --platform manylinux2014_aarch64 --python-version 3.12 -d /tmp/wheels -r requirements.txt が通るかで、移す前に大きさを測れます。
ARM のイメージを x86 の VPS でエミュレーションして動かし続けてもいいですか?
短期間なら使えますが、常用は勧めません。tonistiigi/binfmt で QEMU を登録すれば docker run --platform linux/arm64 で ARM のイメージが動きますが、機械語を実行のたびに変換するので一般に数倍から十倍以上遅くなり、その分の CPU 時間を払い続けることになります。代わりのイメージを探すか、Dockerfile から x86 向けにビルドするか、どちらかができるまでの橋として使ってください。