7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rails+docker-compose.ymlで開発環境を作成しようとしたらnode_modulesが行方不明になった件

Posted at

結論

.dockerignoreで共有外にしていてもdocker-compose.ymlでホストをマウントすると、ホスト側の状態が上書きされnode_modulesが消えていた。
docker-compose.ymlvolumesに以下のようにしてホストとは独立した匿名ボリュームを作成し、上書きを回避する。

docker-compose.yml
# ※myappの部分のディレクトリ名は読み替えてください
volumes:
  - .:/myapp
  - /myapp/node_modules # ホストと独立した匿名ボリュームをマウント

.dockerignoreで指定していれば除外されると勘違いしていた。

顛末

1. 要件

新しく作成するプロダクトの開発環境の要件は以下。

  • Railsのプロダクト
  • Playwrightを利用(特定のサイトをスクレイピングするため)
  • docker composeコマンドで起動
  • ホストにはnode_modulesの内容を反映したくない

2. node_module、消える

Playwrightのダウンロードを含んだイメージのビルドは時間がかかるのと、今回のテーマの本質とはズレるため以下の簡易なDockerfile、docker-compose.ymlを作成しました。
また、.dockerignoreやdatabase.ymlはとりあえず動作する状態に仮で設定しています。

Dockerfile(簡易なものとしてRails7.2でnewしたらデフォルトで作成されるファイルをベースにnode_modulesを手動で作成する処理を追加)
ARG RUBY_VERSION=3.3.5
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base

# Rails app lives here
WORKDIR /rails

# Install base packages
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Set production environment
ENV RAILS_ENV="production" \
    BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development"

# Throw-away build stage to reduce size of final image
FROM base AS build

# Install packages needed to build gems
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential git pkg-config && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
    bundle exec bootsnap precompile --gemfile

# Copy application code
COPY . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile


# Final stage for app image
FROM base

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails


# 実際にnode_modulesをダウンロードすると時間がかかるので簡易的にnode_modulesを作成
# Run and own only the runtime files as a non-root user for security
RUN mkdir node_modules && \
    touch node_modules/hogehoge.txt && \
    groupadd --system --gid 1000 rails && \
    useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
    chown -R rails:rails db log storage tmp && \
    chmod 600 config/master.key && \
    chown rails:rails config/master.key
USER 1000:1000

# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]
docker-compose.yml
services:
  web:
    build:
      context: .
    ports:
      - "3000:3000"
    volumes:
      - .:/rails
.dockerignore(キーの部分だけコメントアウト)
# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.

# Ignore git directory.
/.git/

# Ignore bundler config.
/.bundle

# Ignore all environment files (except templates).
/.env*
!/.env*.erb

# Ignore all default key files.
# /config/master.key
# /config/credentials/*.key

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/.keep

# Ignore storage (uploaded files in development and any SQLite databases).
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/.keep

# Ignore assets.
/node_modules/
/app/assets/builds/*
!/app/assets/builds/.keep
/public/assets
config/database.yml(とりあえずRailsが起動するように設定)
production:
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
  database: storage/production.sqlite3

この設定でdocker-compose up -dコマンドを実行するとRailsサーバーは立ち上がるのですが、コンテナにアタッチするとrails/node_modulesのディレクトリがゴッゾリ消えていました。

3. デバッグ

a. Dockerfileを疑う

以下のコマンドでDockerfileのみで起動し、node_modulesディレクトリを確認します。

docker build -t myapp .
docker run -d -p 3000:3000 --name myapp myapp
docker exec -it myapp ls node_modules
# => hogehoge.txt

これでmyappコンテナにアタッチしてみるとnode_modulesディレクトリの存在を確認できました。
犯人はdocker-compose.ymlに潜んでいそうです。

b. docker-compose.ymlを疑う

docker-compose.ymlファイルを見てみると、どう見てもvolumes:が怪しいです。
上記のdockerコマンドで、portsは指定済みのため違いはvolumesを指定しているかどうかの違いです。

docker-compose.yml
services:
  web:
    build:
      context: .
    ports:
      - "3000:3000"
    volumes:
      - .:/rails # どう見てもここが怪しい

ここで/railsディレクトリマウント時にホストの内容が反映され、node_moduleを潰していると仮説を立てて調査することにしました。

4. ホストをマウント時にnode_modules潰す仮説を調査

調べているとvolumesでホストをマウントしたときにホストの状態で塗りつぶすということでした。勘違いしていたのですが.dockerignoreで指定した内容はビルド時とボリュームマウント時に除外されると思っていたのですが、ビルド時に無視されるだけでボリュームマウント時には除外されないということでした。
それを回避する方法として今回は匿名ボリュームを利用します。

5. 匿名ボリュームを導入

以下のようにして匿名ボリュームを導入します。

docker-compose.yml
services:
  web:
    build:
      context: .
    ports:
      - "3000:3000"
    volumes:
      - .:/rails
      - /rails/node_modules # ホストとは独立したボリュームを利用

匿名ボリュームはホストとは独立したボリュームの指定になり、ホストのマウント時に上書きされることを防ぐことができます。
これにて無事にnode_modulesが行方不明にならずに済みました。

まとめ

.dockerignoreDockerfileCOPYで除外されるが、docker-compose.ymlvolumesでマウントすると除外されないということ。volumesで特定のディレクトリをマウントしたくない場合は匿名ボリュームを利用するということ。

参考

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?