【Heroku】Railsのコンテナをpush時に「Error: docker push exited with Error: 1」

## 事象
下記のDockerfileおよびdocker-compose.yamlで作成したRailsのコンテナをHerokuにpushした際に

```
unknown blob
 ▸    Error: docker push exited with Error: 1
```
とエラーが発生しました。

```dockerfile:Dockerfile
FROM ruby:2.6.3-stretch

RUN apt-get update &&\
    apt-get install -y nodejs build-essential libpq-dev postgresql-client

RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp
```

```yaml:docker-compose.yaml
version: "3"
services:
  db:
    image: postgres:11.4-alpine
    ports:
      - "5432:5432"
    volumes:
      - "./postgres-data:/var/lib/postgresql/data"
  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - ".:/myapp"
    ports:
      - "3000:3000"
    tty: true
    stdin_open: true
    depends_on:
      - db
    working_dir: "/myapp"
```

ちなみに、Herokuへのpushはローカルでコンテナの起動を停止後に下記の手順で行いました。

```console
$ heroku create

$ heroku container:login

$ heroku container:push "コンテナ名"
↑このコマンドで上述のエラーが発生
```

## 原因と対策
エラーメッセージの「unknown blob Error: docker push exited with Error: 1」
を丸ごとコピペして検索すると、[こちら](https://github.com/heroku/cli/issues/1081#issuecomment-441426646)のGitHubのissueを発見しました。

どうやら、コメントを読む限りでは、Herokuでコンテナを用いる際は`CMD`を用いる必要があるようです。

ただ、issueのコメントにあった[こちら](https://devcenter.heroku.com/articles/container-registry-and-runtime#dockerfile-commands-and-runtime)のリンクが`CMD`を用いる必要がある根拠なのかもしれませんが、私にはイマイチ読み取れませんでした。

あえて挙げるとすると
 > CMD will always be executed by a shell so that config vars are made available to your process; to execute single binaries or use images without a shell please use ENTRYPOINT`

が`CMD`に関する記載なのですが、`CMD`が常にシェルによって実行されるから`CMD`を用いる必要があるということなんでしょうか...

いずれにせよ、一旦試しに[Dockerの公式サンプル](https://docs.docker.com/compose/rails/)を参考に下記のようにDockerfileを修正しました。


```dockerfile:Dockerfile
FROM ruby:2.6.3-stretch

RUN apt-get update &&\
    apt-get install -y nodejs build-essential libpq-dev postgresql-client

RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# ↓を追加
CMD ["rails", "server", "-b", "0.0.0.0"] 
```
Dockerfileを修正後に、再度pushしてみると、

```console
・・・(省略)・・・
Your image has been successfully pushed.
You can now release it with the 'container:release' command.
```

とエラーメッセージが消え、pushに成功することができました。

## CMDとは
ここで改めて`CMD`とはなんぞやということを調べてみましたら、非常に良い記事を発見したので、共有します。

@uehaj さんの[DockerfileのCMDとENTRYPOINTを改めて解説する](https://qiita.com/uehaj/items/e6dd013e28593c26372d)という記事です。

この記事によるとCMD指定には下記の2つの意味があるようです。

1. docker runでコマンドを指定する方法において、「docker run」で実際のコマンドを何も指定しなかったときに実行するコマンド(と引数)のデフォルト値
2. DockerfileのENTRYPOINT項目でコマンドを指定する方法において、ENTRYPOINTに指定したコマンドの追加的引数の、コマンドラインから指定しなかった場合のデフォルト値

結局`CMD`には2つ意味がありますが、
> いずれの場合でも、CMDはrunの後に続ける引数のデフォルト値指定であると言える。

とのことです。

## まとめ
コンテナをHerokuにpushした際に

```
unknown blob
 ▸    Error: docker push exited with Error: 1
```
とエラーが発生したら、Dockerfileに`CMD`コマンドが書かれているかをチェックしてみると良いでしょう。

また、今回の調査の過程で、`CMD`は「`docker run`の後に続ける引数のデフォルト値を指定している」ということを学ぶことができました。改めてDockerの公式ドキュメントにも目を通しておこうと思います。

最後までお読みいただきありがとうございました。

## 参考文献

解決のきっかけ
https://github.com/heroku/cli/issues/1081

`CMD`を用いる必要がある根拠と思われるページ
https://devcenter.heroku.com/articles/container-registry-and-runtime#dockerfile-commands-and-runtime

DockerfileのCMDとENTRYPOINTを改めて解説する
https://qiita.com/uehaj/items/e6dd013e28593c26372d