Docker for Windows で postgres のデータを永続化したい

# 背景

Docker for Windows で postgres のデータを永続化したい。
こう書いただけだと、ググれば類例はあまたあるわけですが…

単純に `/var/lib/postgresql/data` をホスト側のパスにマッピングしただけではいろいろエラーが出てまともに動かない。で、 [外部ボリュームを使えばいいじゃない](https://qiita.com/ko-da-k/items/47b96883144a5bf1cb1e) という解決策が Windows の場合は一般的なんだろうと思いますが、私の場合、手元の環境の一部が

* ときどき Docker for Windows が立ち上がらなくなって再インストールを余儀なくされる

んです。これやると、外部ボリュームなんてどこかに吹っ飛んでしまうんですね。何のための永続化なの…(涙)。

最近は WSL2 からも Docker for Windows さわれるようになったし、うまい方法はないかしら…と探していたら、ちゃんとあるじゃないですか。というわけで、以下まとめ。

# 方法

## 概要

ちなみに、ここの記述は [Docker Hub の postgres](https://hub.docker.com/_/postgres) の "Arbitrary --user Notes" に書かれている 3 つの回避策のうちの 2 番：

> bind-mount /etc/passwd read-only from the host (if the UID you desire is a valid user on your host)

を元にしています。

WSL2 の場合、ユーザの UID も GID も 1000 ですので、 postgres コンテナの中のプロセスが 1000:1000 で動作するようにお膳立てすればよいわけです。

## 詳細

### 1. `/etc/passwd`, `/etc/group` への UID=1000, GID=1000 の行の追加

postgres コンテナから `/etc/passwd`, `/etc/group` を取り出して、そこに UID=1000, GID=1000 の行を追加したものをそれぞれ `./etc/passwd`, `./etc/group` として保存します。

* /etc/passwd ... 末尾に UID=1000 の行を加えてあります。

```bash 
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
postgres:x:70:70:Linux User,,,:/var/lib/postgresql:/bin/sh
utmp:x:100:406:utmp:/home/utmp:/bin/false
hogehoge:x:1000:1000:Linux User,,,:/var/lib/postgresql:/bin/sh
```

* /etc/group ... 末尾に GID=1000 の行を加えてあります。

```bash 
root:x:0:root
bin:x:1:root,bin,daemon
daemon:x:2:root,bin,daemon
sys:x:3:root,bin,adm
adm:x:4:root,adm,daemon
tty:x:5:
disk:x:6:root,adm
lp:x:7:lp
mem:x:8:
kmem:x:9:
wheel:x:10:root
floppy:x:11:root
mail:x:12:mail
news:x:13:news
uucp:x:14:uucp
man:x:15:man
cron:x:16:cron
console:x:17:
audio:x:18:
cdrom:x:19:
dialout:x:20:root
ftp:x:21:
sshd:x:22:
input:x:23:
at:x:25:at
tape:x:26:root
video:x:27:root
netdev:x:28:
readproc:x:30:
squid:x:31:squid
xfs:x:33:xfs
kvm:x:34:kvm
games:x:35:
shadow:x:42:
cdrw:x:80:
usb:x:85:
vpopmail:x:89:
users:x:100:games
ntp:x:123:
nofiles:x:200:
smmsp:x:209:smmsp
locate:x:245:
abuild:x:300:
utmp:x:406:utmp
ping:x:999:
nogroup:x:65533:
nobody:x:65534:
postgres:x:70:postgres
hogehoge:x:1000:hogehoge
```

### 2. `/etc/passwd`, `/etc/group` をリードオンリーマウントしつつ、 `user` オプションで UID=GID=1000 を指定してコンテナを起動

私は docker-compose を使っているので docker-compose.yml の記述で示しますが、 `docker run` の場合も同様です。
下記の例を使う場合は事前に `./data/`, `./docker-entrypoint-initdb.d/` フォルダを用意しておくことをお忘れなく。 `./docker-entrypoint-initdb.d/` 配下には、データベースがまだなかった場合(初回起動時)に実行される初期化用のスクリプト(.sh や .sql)を置いておくことができます。

```yaml
version: '3'

services:
  postgres:
    image: postgres:12-alpine
    user: '1000:1000'
    volumes:
      - ./etc/passwd:/etc/passwd:ro
      - ./etc/group:/etc/group:ro
      - ./data:/var/lib/postgresql/data
      - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
    ports:
      - 5432:5432
    environment:
      POSTGRES_PASSWORD: postgres
    networks:
      mynet:

networks:
  mynet:
```

こんなところかな。