Docker の Volume バックアップ（Docker v19.03, macOS/Windows10）

![](https://img.shields.io/badge/Tested%20OS-macOS%2010.14-blue) ![](https://img.shields.io/badge/Tested%20Docker-v19.03-blue)

> `docker volume create` で作成したボリュームをローカルにバックアップしたい。

しかし、Mac や Win10 には `docker volume inspect` で表示される **`Mountpoint` に該当ボリュームがない**のです。

`Docker Engine` のバージョンによっても挙動が違うらしく、**簡単にバックアップ＆復元できないか**と<a href="https://www.google.com/search?q=site:qiita.com+mac+docker+volume+%E3%83%90%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97&tbs=qdr:y" target="blank">「<code>mac</code> <code>docker</code> <code>volume</code> <code>バックアップ</code>」で Qiita 記事をググって</a>も記事の内容がバラバラで困ったので、自分のググラビリティとして。

## TL; DR <sub><sup>（今北産業）</sup></sub>

実行中のコンテナの、<font color="darkorange">**特定のファイルをローカルにコピーしたいだけ**</font>なら、`docker cp` コマンドを使うのが楽です。

```bash:構文
docker cp <コンテナ名 or コンテナID>:<コンテナ内のパス> <出力先のパス>
```

```shell-session:実行例
$ docker cp \
    lancache-dns-1:/opt/cache-domains/cache_domains.json \
    ./cache_domains.json

Successfully copied 4.61kB to /root/lancache/cache_domains.json
```

逆に、<font color="darkorange">**複数ディレクトリやボリュームをバックアップしたいだけ**</font>なら、`Alpine Linux` などの軽量コンテナに、バックアップ元とバックアップ先の<strong>２つのボリュームを一旦マウントし `tar` などでアーカイブするのが<ruby>楽<rt>らく</rt></ruby></strong>です。

<details><summary><code>docker run</code> でバックアップする</summary>

以下は、`docker volume create data-hoge` で作成し、利用されていたボリュームを、ローカルの `./backup` にバックアップしたい場合のコマンドです。

Alpine Linux の軽量イメージからコンテナを作成し、各々をマウントしてアーカイブしています。

1. バックアップしたいボリューム（`-v data-hoge:/data`）
2. バックアップ先のディレクトリ（`-v "$(pwd)/backup":/backup`）

```bash
docker run --rm \
    -v data-hoge:/data \
    -v "$(pwd)/backup":/backup \
    alpine \
    tar cvf /backup/backup.tar /data
```

**復元**は、上記の逆の手順でアーカイブとボリュームをマウントして、ボリューム内に解凍します。

</details>

<details><summary><code>docker compose run</code> でバックアップする（バックアップ・リストア用 <code>docker-compose.yaml</code>）</summary>

ボリュームをバックアップする専用のディレクトリを作成し、バックアップ & リストア用の YAML ファイルを作成しておくと便利です。

特に、`docker system prune` などで、イメージ・コンテナ・ボリュームなどを定期的に `prune` `prune` する場合に重宝します。

```bash:バックアップ
# バックアップの実行（カレントの ./backup/backup.tar.gz に sample-data ボリュームがアーカイブされる）
docker compose run --rm backup
```
```bash:リストア
# リストア先のボリュームを作成
docker volume create sample-data
# リストアの実行（カレントにある ./backup/backup.tar.gz をリストア先に展開する）
docker compose run --rm restore
```

```yaml:docker-compose.yml
# 下記 sample-data を適宜バックアップしたいボリューム名に変更する
version: "3.9"

volumes:
  sample-data:
    external: true

services:
  backup:
    image: alpine
    volumes:
      - sample-data:/fromA
      - ./backup:/toB
    working_dir: /fromA
    entrypoint: [ "tar",  "czvf", "/toB/backup.tar.gz", "."]

  restore:
    image: alpine
    volumes:
      - ./backup/backup.tar.gz:/fromA/backup.tar.gz
      - sample-data:/toB
    working_dir: /fromA
    entrypoint: [ "tar",  "xzvf", "/fromA/backup.tar.gz", "-C", "/toB"]
```

</details>

## TS; DR <sub><sup>（データの永続化を完全に理解した気になるコマケーこと）</sup></sub>

### 「データ消えちゃうんだよね」「永続化っ Σ👋 ﾊﾞｼｯ」

基本的に Docker のコンテナが削除されると、コンテナ内のデータは消えます。当然ですが。

そこで、コンテナ内で作成・更新されたデータを残すことを「データの永続化」と言います。

Docker コンテナのデータ永続化には、いくつか種類があります。

1. Dockerfile と同じ階層のディレクトリをマウントして、永続化したいデータをそこに置く
2. Docker ボリュームを作成＆マウントして、永続化したいデータをそこに置く

上記１の**ローカル・ディレクトリをマウントするのが簡単**なので、一般的です。

しかし「複数の異なるコンテナで同じデータを共有したい」場合に問題が発生します。例えば、複数コンテナで同じディレクトリをマウントしたい場合などです。

この上記１の問題は、コンテナごとに Dockerfile を分けていた場合です。各々のディレクトリが異なるため、共有したいディレクトリの階層が異なることになり、マウントできません。

```shell-session:dataディレクトリをマウントしたい
$ tree
.
├── docker-compose.yml <-- ./data と ./sample1/data はコンテナと
│                          同じ階層以下にあるのでマウントできる。
├── data <---------------- sample1, sample2 のコンテナからは階層
│   │                      が異なるため相対パスではマウントできない。
│   ├── common1.json
│   ├── data2.json
│   └── data3.json
├── sample1
│   ├── data <------------ sample1 のコンテナはマウントできる。
│   │   │                  sample2 のコンテナからは相対パスではマウ
│   │   │                  ントできない。
│   │   ├── common1.json
│   │   ├── data2.json
│   │   └── data3.json
│   └── Dockerfile
└── sample2 <------------- (´・ω・｀)ｼｮﾎﾞｰﾝ
    └── Dockerfile
```

マウントできないと言いましたが、**絶対パスで指定すればマウントはできます**。しかし、パスは環境によって変わるものなので相対パスで指定したいところですが、できないのが問題です。

:::note info
実は Windows や macOS の場合、絶対パスでも Docker Desktop の設定で親ディレクトリのパスを通していないとマウントできません。[`Preferences`] -> [`Resources`] -> [`File sharing`] に登録されているディレクトリ以下のパスのみが Docker から参照・マウントできます。

Docker を<ruby>ふいんき<rt>・・・・</rt></ruby>で触っているものだから、セキュリティが心配になり、「なんで `/Users` ディレクトリがあるの」と、気持ち悪いので親ディレクトリの登録を削除しちゃったのを失念していた、といった意外に見落としがちな設定箇所だったりします。
:::

これは Windows や macOS の Docker（Docker Desktop）の場合のセキュリティ上の制限です。具体的には Dockerfile のある階層以下のディレクトリしか相対パスでマウントできないように制限されているからです。

つまり、`./data` は使えても `../data` は使えないと言うことです。

そのような場合、3 つの方法があります。

1. あきらめて絶対パスでマウントする
2. 上階層に `docker-compose` を置いてマウントする
3. Docker ボリュームを作成＆マウントする

この、最後の 3 目の方法が本記事の本題です。

`$ docker volume create` コマンドで仮想ボリュームを作成し、それを各々のコンテナにマウントする方法です。

特に、この方法だと**ホスト側のディスク・フォーマットを気にしなくてもいい**というメリットがあります。

問題は、良くも悪くも「仮想ボリューム」であることです。

なぜなら、この仮想ボリューム内のデータをローカルにバックアップするのが Windows や macOS の場合、いささか面倒だからです。詳しくは下記「[Mac や Windows の Docker ボリュームは特殊](#mac-%E3%82%84-windows-%E3%81%AE-docker-%E3%83%9C%E3%83%AA%E3%83%A5%E3%83%BC%E3%83%A0%E3%81%AF%E7%89%B9%E6%AE%8A)」参照。

### Docker ボリュームのバックアップ

まずは、具体的な手順から。

いささか冗長的ですが、「完全に理解した」気分になれるように丁寧に説明したいと思います。

```shell-session:作成済み（バックアップしたい）Dockerボリュームの確認
$ docker volume ls
DRIVER              VOLUME NAME
local               data-hoge
```

`docker` の `ls` コマンド（`docker volume ls`）を使うと、作成済みの仮想ボリュームが一覧で確認できます。上記の場合、`data-hoge` がすでに作成済みとなります。

```shell-session:ボリュームがない場合の新規作成
$ docker volume ls
DRIVER              VOLUME NAME

$ docker volume create data-hoge
data-hoge

$ docker volume ls
DRIVER              VOLUME NAME
local               data-hoge
```

任意の仮想ボリュームを作成したい場合は `create` コマンド（`docker volume create`）を使います。上記は `data-hoge` を新たに作成しています。

それでは、とあるコンテナに `data-hoge` がマウントされて、中にデータが保存されていると仮定します。この中身をローカルの `~/my_backup/backup` にバックアップしたいと思います。

```shell-session:バックアップ
$ # バックアップの作業ディレクトリの作成＆移動
$ cd mkdir ~/my_backup && cd "$_"

$ # バックアップ開始（/data を /backup にアーカイブする）
$ docker run --rm \
    -v data-hoge:/data \
    -v $(pwd)/backup:/backup \
    alpine \
    tar cvf /backup/backup.tar /data
tar: removing leading '/' from member names
data/
data/hello.txt
...
```

上記は、作業ディレクトリ `~/my_backup` に移動後、まっさらな Alpine linux のコンテナを立ち上げて 2 つのボリュームをマウントしています。

具体的には `data-hoge` の仮想ボリュームをコンテナの `/data` に、ローカルの `./backup` ディレクトリをボリュームとしてコンテナの `/backup` にマウントしています。

あとは `tar` コマンドでコンテナの `/backup/backup.tar` に `/data` ディレクトリの中身をアーカイブ（固めて 1 つに）しています。

これにより、ローカルの `~/my_backup/backup/` ディレクトリに `backup.tar` が作成されます。

バックアップからリカバリしたい場合は、アーカイブする変わりに `-xvf` でアーカイブを展開します。（圧縮→解凍みたいなものです）

```shell-session:アーカイブの解凍と確認
$ # 作業ディレクトリに移動
$ cd ~/my_backup

$ # 作成されたバックアップの確認
$ ls
backup

$ # ディレクトリ構造の確認（要treeコマンド。brew install tree でインストール可能）
$ tree
.
└── backup
    └── backup.tar

1 directory, 1 file

$ # アーカイブの解凍
$ tar -xvf ./backup/backup.tar
x data/
x data/hello.txt
...

$ # 解凍ファイルの確認
$ ls
backup	data

$ # ディレクトリ構造の確認
$ tree
.
├── backup
│   └── backup.tar
└── data
    ├── ...
    └── hello.txt
```

### Mac や Windows の Docker ボリュームは特殊

`docker volume create --name data-hoge` とボリュームを作成した場合、`inspect` コマンドでボリューム情報を表示すると以下のようになります。

```shell-session:ボリューム情報の表示
$ docker volume inspect data-hoge
[
    {
        "CreatedAt": "2019-12-09T04:28:16Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/data-hoge/_data",
        "Name": "data-hoge",
        "Options": {},
        "Scope": "local"
    }
]
```

しかし、上記の `Mountpoint` のパスにデータがあるかと思いきや、ありません。

```shell-session:macOSでMountpointをlsしてみる
$ ls /var/lib/docker/volumes/data-hoge/_data
ls: /var/lib/docker/volumes/data-hoge/_data: No such file or directory

$ # そもそも /var/lib/docker がない。docker いっちゃった？
$ ls /var/lib/
postfix
```

```shell-session:Win10+WindowsTerminal(Preview)でMountpointをlsしてみる
> docker volume ls
 DRIVER              VOLUME NAME 
local               data-hoge 
> docker volume inspect sample
 [ 
    {
         "CreatedAt": "2020-03-19T14:09:56Z",
         "Driver": "local",
         "Labels": {},
         "Mountpoint": "/var/lib/docker/volumes/data-hoge/_data",
         "Name": "sample",
         "Options": {}, 
        "Scope": "local" 
    }
 ] 

PS > ls /var/lib/docker/volumes/data-hoge/_data
ls : パス 'C:\var\lib\docker\volumes\data-hoge\_data ' が存在しないため検出できません。
発生場所  行:1 文字:1
+ ls /var/lib/docker/volumes/data-hoge/_data 
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    + CategoryInfo          : ObjectNotFound: (C:\var\lib\docker\volumes\data-hoge\_data:String) [Get-ChildItem], ItemNot      FoundException 
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand  

PS > # やっぱり /var/lib/ は存在しない。docker いっちゃってる。
PS > ls /var/lib/
ls : パス 'C:\var\lib\ ' が存在しないため検出できません。
発生場所  行:1 文字:1
+ ls /var/lib/
 + ~~~~~~~~~~~~
     + CategoryInfo          : ObjectNotFound: (C:\var\lib\:String) [Get-ChildItem], ItemNotFoundException
      + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemComman
```

これは Mac や Windows10 の Docker Desktop の場合、Docker は [VM（仮想マシン）](https://ja.wikipedia.org/wiki/%E4%BB%AE%E6%83%B3%E6%A9%9F%E6%A2%B0) 上で動いているためです。

つまり **`docker volumes create` で作成されたボリュームも仮想マシンのイメージの中に作成される**ということなので、ローカルには直接作成されません。

以下は macOS の場合ですが、作成された仮想マシンのイメージは以下のディレクトリに設置されます。（`Docker v19.03.5` 現在）

```bash:Dockerの仮想マシン・イメージの保存先
~/Library/Containers/com.docker.docker/Data/vms/0/
```

問題は、このディレクトリから該当するイメージと、そこからデータを引き出す方法がわかりづらいこと。

```shell-session:どのファイルを選べばいいのかわからない（treeコマンドはbrewで別途インストール済み）
$ tree ~/Library/Containers/com.docker.docker/Data/vms/0/
/Users/admin/Library/Containers/com.docker.docker/Data/vms/0/
├── 00000002.000005f4
├── 00000002.00001000
├── 00000002.00001001
├── 00000002.00001002
├── 00000002.0000f3a4
├── 00000002.0000f3a5
├── 00000003.000005f5
├── 00000003.00000948
├── Docker.raw
├── config.iso
├── connect
├── data
├── guest.000005f5 -> 00000003.000005f5
├── guest.00000948 -> 00000003.00000948
├── hyperkit.json
├── hyperkit.pid
├── lifecycle-server.sock
├── log
├── nic1.uuid
└── tty -> /dev/ttys000

2 directories, 18 files
```

[`screen` と `tty` で仮想マシンに接続する方法](https://qiita.com/amuyikam/items/938781ff5898e654fd7c)もあるようなのですが、確認ができるというだけでローカルにデータを保存するには煩雑な作業が必要そうです。

```shell-session
$ # 仮想マシンの tty に接続
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
cd
docker-desktop:~# # 仮想マシン上で Mountpoint を確認
docker-desktop:~# ls /var/lib/docker/volumes/data-qithub/_data
hello.txt ...

docker-desktop:~# # Ctrl+a -> Crtl+k で exit
Really kill this window [y/n] y
[screen is terminating]
```

これらのファイルを下手に触って、うっかりイメージを壊したりしそうで危険な臭いがします。また、`docker volume archive <ボリューム名>` のようなコマンドもないようです。(´・ω・｀)

```shell-session
$ docker volume --help

Usage:	docker volume COMMAND

Manage volumes

Commands:
  create      Create a volume
  inspect     Display detailed information on one or more volumes
  ls          List volumes
  prune       Remove all unused local volumes
  rm          Remove one or more volumes

Run 'docker volume COMMAND --help' for more information on a command.
```

そこで、**確実で安全なバックアップ方法がないか探したところ、公式に書いてありました**。ダミーのコンテナに、ボリュームとローカルのディレクトリをマウントして `tar` アーカイブする方法です。（訳は、筆者訳）

> For example, create a new container named `dbstore`:
> 【訳】 例えば、まず "`dbstrore`" というコンテナを新規に作成します。

> ```bash
> $ docker run -v /dbdata --name dbstore ubuntu /bin/bash
> ```

> Then in the next command, we:
>
> - Launch a new container and mount the volume from the `dbstore` container
> - Mount a local host directory as `/backup`
> - Pass a command that tars the contents of the `dbdata` volume to a `backup.tar` file inside our `/backup` directory.
>
> 【訳】 次のコマンドで、以下を行いましょう。
>
> - 別の新しいコンテナを起動し、先の `dbstore` コンテナからボリュームをマウントする。
> - ローカルのディレクトリを `/backup` としてマウントする。
> - `tar` コマンドで、`dbdata` ボリュームの内容をアーカイブして、`/backup` ディレクトリ内の `backup.tar` ファイルに格納する。

> ```bash:既存のコンテナのボリュームを新規コンテナにマウントしてアーカイブする方法
> $ docker run --rm \
>     --volumes-from dbstore \
>     -v "$(pwd)":/backup \
>     ubuntu tar cvf /backup/backup.tar /dbdata
> ```
> （[Backup, restore, or migrate data volumes](https://docs.docker.com/storage/volumes/#backup-restore-or-migrate-data-volumes) @ docs.docker.com より）

確かにシンプルで力強い方法です。なにより直感的（覚えやすい）ですな。

ただ、`ubuntu` コンテナは 78MB 程度とは言え、筆者は `alpine` の軽量コンテナ（6 MB）が個人的に好みです。

### バックアップとリストア

公式にある上記の方法だと `ubuntu` コンテナであるだけでなく**処理後に不要になったダミー・コンテナが残ってしまいます**。ボリュームが生きている（`docker volume ls` で表示される）のであれば、以下のようにバックアップするのが簡単だと思います。

```bash:バックアップ方法
$ # バックアップしたいボリューム名がdbstoreの場合
$ docker run --rm \
    -v dbstore:/dbstore \
    -v "$(pwd)/backup":/backup \
    alpine \
    tar cvf /backup/backup.tar /dbstore
```

ちなみに、リカバリーは逆の手順で、ボリューム内に `tar` ファイルを解凍します。

```bash:リカバリー方法
$ # リカバリー用の空のボリューム作成 （dbstore2）
$ docker volume create --name dbstore2

$ # リカバリー（Ubuntu イメージにリカバリする場合）
$ docker run --rm \
    -v dbstore2:/dbdata \
    -v "$(pwd)/backup.tar":/backup/backup.tar \
    ubuntu \
    bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"
```

なお、上記の `$(pwd)` の箇所ですが、macOS や Windows はスペースの入ったパスが多いため、<strong>`"$(pwd)"` とダブルクォートで囲うクセを付けておくと良い</strong>と思います。

## 参考文献

- [Docker Volume (特に volumeタイプ) のわかりづらいところを説明してみる](https://qiita.com/mtgto/items/aabc212a1d3f99878828#%E3%83%9C%E3%83%AA%E3%83%A5%E3%83%BC%E3%83%A0%E3%81%AE%E3%83%90%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%83%AA%E3%82%B9%E3%83%88%E3%82%A2%E3%81%AF%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%92%E4%BB%8B%E3%81%97%E3%81%A6%E8%A1%8C%E3%81%86) @ Qiita
- [Docker for MacのDisk Imageの場所が変わった](https://qiita.com/amuyikam/items/938781ff5898e654fd7c) @ Qiita
- [dockerのデータボリュームとそのバックアップ方法](https://qiita.com/74th/items/41393f506d223850f2c3) @ Qiita
- [Docker for Macのvolumesの場所](https://qiita.com/uni-3/items/9e912d0d8aa87a398cfc) @ Qiita
- [データ・ボリュームのバックアップ・修復・移行](http://docs.docker.jp/engine/userguide/dockervolumes.html#id9) | コンテナでデータを管理する @ Docker 1.9 beta 日本語ドキュメント