RailsアプリにDockerとCircleCIを導入した際、DB周りでエラーになったときの対処法

こんにちは、[ペーパーエンジニアのよしこ](https://twitter.com/K2_Yoshiko)です。

CircleCIで`git push`時に自動テスト（RSpec）しているRailsアプリに、Dockerを導入しました。

その際にデータベース周りで躓いたエラーがあります。

同じエラーの報告が少なかったので、エラー解消した対処法を共有します。

# エラー文

`git push`をトリガーにCircleCIがテストを実行するのですが、その前にデータベースを構築します。
そこでエラーが出ていました。

``` .circleci/config.yml
# DBをセットアップ
- run:
    name: DBをセットアップ
    command: bin/rails db:schema:load --trace
```

※全文は下記


``` CircleCI
  #!/bin/bash -eo pipefail
bin/rails db:schema:load --trace
** Invoke db:schema:load (first_time)
** Invoke environment (first_time)
** Execute environment
** Invoke db:load_config (first_time)
** Execute db:load_config
** Invoke db:check_protected_environments (first_time)
** Invoke environment 
** Invoke db:load_config 
** Execute db:check_protected_environments
rails aborted!
PG::ConnectionBad: could not translate host name "db" to address: Name or service not known
/home/circleci/project/vendor/bundle/gems/pg-0.20.0/lib/pg.rb:56:in `initialize'
.
.  省略
.
bin/rails:4:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => db:schema:load => db:check_protected_environments

Exited with code exit status 1
CircleCI received exit code 1
```

# 環境

Ruby : 2.6.3
Rails : 5.1.6
postgres :  12.2
CircleCI : 2.1
Docker-compose version: '3'

# 結論

`database.yml`に、test環境で`host: localhost`を追加することで解決しました。

``` config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  
  username: postgres
  password: 
  host: db
  timeout: 5000

development:
  <<: *default
  database: haito_notice_development

test:
  <<: *default
  database: haito_notice_test
  host: localhost    # <<<<<追加<<<<<

production:
  <<: *default
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

```

Dockerのimage上では`db`としてDB構築していますので、develop環境やproduction環境では`host: db`とする必要があります。

しかし私の場合は、CircleCIのDocker imageをtest環境かつ`host: 127.0.0.1 (localhost)`で構築しているため、これに合わせてあげる必要があったのではと理解しています。


# CircleCI

基本、公式のドキュメントを元に記載していますが、
別のエラー対処のためにworkflowsやcommandsを現在は適用していないため参考程度に。


``` .circleci/config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
        environment:
          BUNDLE_JOBS: 3
          BUNDLE_RETRY: 3
          BUNDLE_PATH: vendor/bundle
          PGHOST: 127.0.0.1
          PGUSER: postgres
          RAILS_ENV: test
      - image: circleci/postgres:12-alpine
        environment:
          POSTGRES_USER: postgres
          POSTGRES_DB: app_test
    steps:
      - checkout
      - restore_cache:
          name: 依存関係キャッシュを復元
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            - v1-dependencies-
      - run:
          name: Bundler を指定
          command: bundle -v
      - run:
          name: バンドルをインストール
          command: bundle check || bundle install
      - save_cache:
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      - run:
          name: 静的コード解析を実行（RuboCop）
          command: bundle exec rubocop
      - run:
          name: DBの起動まで待機
          command: dockerize -wait tcp://localhost:5432 -timeout 1m
      - run:
          name: DBをセットアップ
          command: bin/rails db:schema:load --trace
          # ここでエラーが起こっていました！！！
      - run:
          name: テストを実行（RSpec）
          command: |
            bundle exec rspec --profile 10 \
                              --format RspecJunitFormatter \
                              --out test_results/rspec.xml \
                              --format progress \
                              $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
      - store_test_results:
          path: test_results
```




# Docker

念の為記載します。
こちらも[Docker公式のドキュメント](https://docs.docker.com/compose/rails/)を参考に作っています。

``` 
# Dockerfile

FROM ruby:2.6.3
RUN apt-get update -qq && \
    apt-get install -y nodejs \
                      postgresql-client

RUN mkdir /app
ENV APP_ROOT /app
WORKDIR $APP_ROOT

COPY ./Gemfile $APP_ROOT/Gemfile
COPY ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install
COPY ./ $APP_ROOT

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
```


```entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```

``` docker-compose.yml
version: '3'
services:
  db:
    image: postgres:12-alpine
    environment:
      POSTGRES_HOST_AUTH_METHOD: 'trust'
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db
```

ご指摘やご不明な点などがございましたらお気軽にご連絡ください。