Dockerによる開発環境構築(Rails & Postgres)において exited with code 0 でコンテナが終了してしまった時の対処法

## 事象
Dockerを用いてRails,PostgreDBの開発環境を構築していた際に、
アプリケーションのコンテナが `exited with code 0` となってしまいました。
これによりコンテナの起動が終了してしまい、`docker exec` でコンテナに入れないという事象が発生しました。

### 問題が発生したコード

``` 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
    depends_on:
      - db
    working_dir: "/myapp"
```

``` ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 5.2.3'
```

## エラーの詳細
上記のファイルを作成し、`docker-compose up --build` とすると下記のメッセージが出力されました。

```console
Creating railsrspec_db_1 ... done
Creating railsrspec_web_1 ... done
Attaching to railsrspec_db_1, railsrspec_web_1
db_1   | The files belonging to this database system will be owned by user "postgres".
db_1   | This user must also own the server process.
db_1   | 
db_1   | The database cluster will be initialized with locale "en_US.utf8".
db_1   | The default database encoding has accordingly been set to "UTF8".
db_1   | The default text search configuration will be set to "english".
db_1   | 
db_1   | Data page checksums are disabled.
db_1   | 
db_1   | fixing permissions on existing directory /var/lib/postgresql/data ... ok
db_1   | creating subdirectories ... ok
db_1   | selecting default max_connections ... 100
db_1   | selecting default shared_buffers ... 128MB
db_1   | selecting default timezone ... UTC
db_1   | selecting dynamic shared memory implementation ... posix
db_1   | creating configuration files ... ok
web_1  | Usage:
web_1  |   rails new APP_PATH [options]
web_1  | 
web_1  | Options:
web_1  |       [--skip-namespace], [--no-skip-namespace]            # Skip namespace (affects only isolated applications)
web_1  |   -r, [--ruby=PATH]                                        # Path to the Ruby binary of your choice
web_1  |                                                            # Default: /usr/local/bin/ruby
web_1  |   -m, [--template=TEMPLATE]                                # Path to some application template (can be a filesystem path or URL)
web_1  |   -d, [--database=DATABASE]                                # Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/frontbase/ibm_db/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)
web_1  |                                                            # Default: sqlite3
web_1  |       [--skip-yarn], [--no-skip-yarn]                      # Don't use Yarn for managing JavaScript dependencies
web_1  |       [--skip-gemfile], [--no-skip-gemfile]                # Don't create a Gemfile
web_1  |   -G, [--skip-git], [--no-skip-git]                        # Skip .gitignore file
web_1  |       [--skip-keeps], [--no-skip-keeps]                    # Skip source control .keep files
web_1  |   -M, [--skip-action-mailer], [--no-skip-action-mailer]    # Skip Action Mailer files
web_1  |   -O, [--skip-active-record], [--no-skip-active-record]    # Skip Active Record files
web_1  |       [--skip-active-storage], [--no-skip-active-storage]  # Skip Active Storage files
web_1  |   -P, [--skip-puma], [--no-skip-puma]                      # Skip Puma related files
web_1  |   -C, [--skip-action-cable], [--no-skip-action-cable]      # Skip Action Cable files
web_1  |   -S, [--skip-sprockets], [--no-skip-sprockets]            # Skip Sprockets files
web_1  |       [--skip-spring], [--no-skip-spring]                  # Don't install Spring application preloader
web_1  |       [--skip-listen], [--no-skip-listen]                  # Don't generate configuration that depends on the listen gem
web_1  |       [--skip-coffee], [--no-skip-coffee]                  # Don't use CoffeeScript
web_1  |   -J, [--skip-javascript], [--no-skip-javascript]          # Skip JavaScript files
web_1  |       [--skip-turbolinks], [--no-skip-turbolinks]          # Skip turbolinks gem
web_1  |   -T, [--skip-test], [--no-skip-test]                      # Skip test files
web_1  |       [--skip-system-test], [--no-skip-system-test]        # Skip system test files
web_1  |       [--skip-bootsnap], [--no-skip-bootsnap]              # Skip bootsnap gem
web_1  |       [--dev], [--no-dev]                                  # Setup the application with Gemfile pointing to your Rails checkout
web_1  |       [--edge], [--no-edge]                                # Setup the application with Gemfile pointing to Rails repository
web_1  |       [--rc=RC]                                            # Path to file containing extra configuration options for rails command
web_1  |       [--no-rc], [--no-no-rc]                              # Skip loading of extra configuration options from .railsrc file
web_1  |       [--api], [--no-api]                                  # Preconfigure smaller stack for API only apps
web_1  |   -B, [--skip-bundle], [--no-skip-bundle]                  # Don't run bundle install
web_1  |       [--webpack=WEBPACK]                                  # Preconfigure for app-like JavaScript with Webpack (options: react/vue/angular/elm/stimulus)
web_1  | 
web_1  | Runtime options:
web_1  |   -f, [--force]                    # Overwrite files that already exist
web_1  |   -p, [--pretend], [--no-pretend]  # Run but do not make any changes
web_1  |   -q, [--quiet], [--no-quiet]      # Suppress status output
web_1  |   -s, [--skip], [--no-skip]        # Skip files that already exist
web_1  | 
web_1  | Rails options:
web_1  |   -h, [--help], [--no-help]        # Show this help message and quit
web_1  |   -v, [--version], [--no-version]  # Show Rails version number and quit
web_1  | 
web_1  | Description:
web_1  |     The 'rails new' command creates a new Rails application with a default
web_1  |     directory structure and configuration at the path you specify.
web_1  | 
web_1  |     You can specify extra command-line arguments to be used every time
web_1  |     'rails new' runs in the .railsrc configuration file in your home directory.
web_1  | 
web_1  |     Note that the arguments specified in the .railsrc file don't affect the
web_1  |     defaults values shown above in this help message.
web_1  | 
web_1  | Example:
web_1  |     rails new ~/Code/Ruby/weblog
web_1  | 
web_1  |     This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
railsrspec_web_1 exited with code 0
db_1   | running bootstrap script ... ok
db_1   | performing post-bootstrap initialization ... sh: locale: not found
db_1   | 2019-08-20 23:05:03.135 UTC [27] WARNING:  no usable system locales were found
db_1   | ok
db_1   | syncing data to disk ... ok
db_1   | 
db_1   | Success. You can now start the database server using:
db_1   | 
db_1   |     pg_ctl -D /var/lib/postgresql/data -l logfile start
db_1   | 
db_1   | 
db_1   | WARNING: enabling "trust" authentication for local connections
db_1   | You can change this by editing pg_hba.conf or using the option -A, or
db_1   | --auth-local and --auth-host, the next time you run initdb.
db_1   | ****************************************************
db_1   | WARNING: No password has been set for the database.
db_1   |          This will allow anyone with access to the
db_1   |          Postgres port to access your database. In
db_1   |          Docker's default configuration, this is
db_1   |          effectively any other container on the same
db_1   |          system.
db_1   | 
db_1   |          Use "-e POSTGRES_PASSWORD=password" to set
db_1   |          it in "docker run".
db_1   | ****************************************************
db_1   | waiting for server to start....2019-08-20 23:05:13.391 UTC [32] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db_1   | 2019-08-20 23:05:13.469 UTC [33] LOG:  database system was shut down at 2019-08-20 23:05:10 UTC
db_1   | 2019-08-20 23:05:13.505 UTC [32] LOG:  database system is ready to accept connections
db_1   |  done
db_1   | server started
db_1   | 
db_1   | /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
db_1   | 
db_1   | waiting for server to shut down...2019-08-20 23:05:13.590 UTC [32] LOG:  received fast shutdown request
db_1   | 2019-08-20 23:05:13.593 UTC [32] LOG:  aborting any active transactions
db_1   | 2019-08-20 23:05:13.597 UTC [32] LOG:  background worker "logical replication launcher" (PID 39) exited with exit code 1
db_1   | 2019-08-20 23:05:13.598 UTC [34] LOG:  shutting down
db_1   | .2019-08-20 23:05:13.653 UTC [32] LOG:  database system is shut down
db_1   |  done
db_1   | server stopped
db_1   | 
db_1   | PostgreSQL init process complete; ready for start up.
db_1   | 
db_1   | 2019-08-20 23:05:13.731 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
db_1   | 2019-08-20 23:05:13.732 UTC [1] LOG:  listening on IPv6 address "::", port 5432
db_1   | 2019-08-20 23:05:13.742 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db_1   | 2019-08-20 23:05:13.816 UTC [41] LOG:  database system was shut down at 2019-08-20 23:05:13 UTC
db_1   | 2019-08-20 23:05:13.847 UTC [1] LOG:  database system is ready to accept connections
```

長いのでピックアップすると
`railsrspec_web_1 exited with code 0`
となっていまして、アプリケーションのコンテナの起動が終了してしまっていました。

下記のようにpsコマンドで調べてみるとやはりコンテナが起動していません。

```console
$ docker-compose ps
      Name                    Command               State            Ports         
-----------------------------------------------------------------------------------
railsrspec_db_1    docker-entrypoint.sh postgres    Up       0.0.0.0:5432->5432/tcp
railsrspec_web_1   bundle exec rails s -p 300 ...   Exit 0  
```

## 解決法
https://github.com/docker/compose/issues/5016#issuecomment-315699169
を参考に`stdin_open: true`をdocker-compose.yamlに追加した後で、
再度 `docker-compose up --build`すると無事にコンテナが起動したままの状態になりました。

ちなみにpsコマンドで調べてみるとこんな感じになります。

```console
$ docker-compose ps
      Name                    Command               State           Ports         
----------------------------------------------------------------------------------
railsrspec_db_1    docker-entrypoint.sh postgres    Up      0.0.0.0:5432->5432/tcp
railsrspec_web_1   bundle exec rails s -p 300 ...   Up      0.0.0.0:3000->3000/tcp
```

一応解決した状態のdocker-compose.yamlを書いておきます。

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

## 原因
今回解決した方法として`stdin_open: true`を追加しました。
stdinつまり標準入力を受け付ける状態にしたわけです。
これによりキーボード等を通じて入力操作が可能になったため、コンテナが起動し続けるようになったと現時点では解釈しています。
（理解に誤りがあればご指摘ください🙇‍♂️）

ちなみに一旦`stdin_open: true`を追加したyamlファイルでコンテナを作成した後で、
`stdin_open: true`を削除してもコンテナが起動し続けるままの状態でした。
コンテナに常にstdinをopenするように設定されたということなんでしょうかね。

## まとめ
docker-compose.yamlを用いて開発環境を構築する際に`exited with code 0`が発生してしまったら、
`stdin_open: true`を追加してみてください。

少しでも誰かのお役に立てれば幸いです。
最後までお読みいただきありがとうございました。

## 参考文献

解決のきっかけになった投稿
https://github.com/docker/compose/issues/5016#issuecomment-315699169

Docker composeファイルリファレンス
http://docs.docker.jp/compose/compose-file.html

Docker runリファレンス
http://docs.docker.jp/engine/reference/run.html

標準入力の概要
https://qiita.com/angel_p_57/items/03582181e9f7a69f8168