はじめに
Rails環境をDockerを利用して環境構築していたのですが、Nginxをつないだところエラーが発生してほかの記事があまり参考にならなかったため自分なりにまとめていきます。
主にWSL2でDockerを使っている方がこのエラーの対象になるかと思います。
問題
DockerでRailsとNginxの環境を用意して、Railsコンテナのユーザーをapp
(rootではない)にdocker-compose.ymlで変更した状態でコンテナを起動すると以下のエラーが発生します。
rails | * Listening on http://0.0.0.0:3000
rails | * Listening on unix:///myapp/tmp/sockets/puma.sock
rails | Use Ctrl-C to stop
rails | bundler: failed to load command: puma (/usr/local/bundle/bin/puma)
rails | /usr/local/bundle/gems/puma-5.5.2/lib/puma/runner.rb:123:in `reopen': Permission denied @ rb_io_reopen - /myapp/log/puma.stdout.log (Errno::EACCES)
rails | from /usr/local/bundle/gems/puma-5.5.2/lib/puma/runner.rb:123:in `redirect_io'
rails | from /usr/local/bundle/gems/puma-5.5.2/lib/puma/single.rb:56:in `run'
rails | from /usr/local/bundle/gems/puma-5.5.2/lib/puma/launcher.rb:181:in `run'
rails | from /usr/local/bundle/gems/puma-5.5.2/lib/puma/cli.rb:80:in `run'
rails | from /usr/local/bundle/gems/puma-5.5.2/bin/puma:10:in `<top (required)>'
rails | from /usr/local/bundle/bin/puma:25:in `load'
rails | from /usr/local/bundle/bin/puma:25:in `<top (required)>'
rails | from /usr/local/lib/ruby/3.0.0/bundler/cli/exec.rb:58:in `load'
rails | from /usr/local/lib/ruby/3.0.0/bundler/cli/exec.rb:58:in `kernel_load'
rails | from /usr/local/lib/ruby/3.0.0/bundler/cli/exec.rb:23:in `run'
rails | from /usr/local/lib/ruby/3.0.0/bundler/cli.rb:478:in `exec'
rails | from /usr/local/lib/ruby/3.0.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
rails | from /usr/local/lib/ruby/3.0.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
rails | from /usr/local/lib/ruby/3.0.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
rails | from /usr/local/lib/ruby/3.0.0/bundler/cli.rb:31:in `dispatch'
rails | from /usr/local/lib/ruby/3.0.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
rails | from /usr/local/lib/ruby/3.0.0/bundler/cli.rb:25:in `start'
rails | from /usr/local/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/libexec/bundle:49:in `block in <top (required)>'
rails | from /usr/local/lib/ruby/3.0.0/bundler/friendly_errors.rb:103:in `with_friendly_errors'
rails | from /usr/local/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/libexec/bundle:37:in `<top (required)>'
rails | from /usr/local/bin/bundle:23:in `load'
rails | from /usr/local/bin/bundle:23:in `<main>'
rails exited with code 1
Permission denied @ rb_io_reopen - /myapp/log/puma.stdout.log (Errno::EACCES)
ということでpuma.stdout.logが権限的にみれないよとエラーを出しています。
調査
これはpuma
のログがコンテナ内でroot権限で作成されることが原因です。
私の作成したdocker-compose.yml
は以下のようになっています。
version: "3.9"
services:
rails:
build: .
container_name: rails
command: bundle exec puma -C config/puma.rb
volumes:
- .:/myapp
- public-data:/myapp/public
- tmp-data:/myapp/tmp
- log-data:/myapp/log
env_file:
- .env
depends_on:
- db
environment:
WEBPACKER_DEV_SERVER_HOST: webpacker
user: app
webpacker:
build: .
volumes:
- .:/myapp
command: ./bin/webpack-dev-server
environment:
WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
ports:
- "3035:3035"
db:
image: mysql:8.0.27
container_name: db
environment:
TZ: Asia/Tokyo
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
ports:
- "3306:3306"
volumes:
- db:/var/lib/mysql
web:
build:
context: containers/nginx
volumes:
- public-data:/myapp/public
- tmp-data:/myapp/tmp
ports:
- 80:80
depends_on:
- rails
volumes:
db:
driver: local
bundle:
driver: local
public-data:
tmp-data:
log-data:
ここで注目してほしいのはrails
コンテナのuser: app
という箇所でユーザーを指定しているところです。これでコンテナ内でrails gなどでファイル生成してもappユーザー
(uid=1000)で作成されるため、WSL2のユーザーと権限が同じなので編集ができるようになります。この設定をしないとrootユーザーでファイルが作成されるためWSL2ユーザーでは編集ができません。
しかし、app
を指定したことでpuma
ログがroot
権限で作成されているため編集できずエラーとなっていました。
ここで考えられるのは以下の方法です。
- WSL2をrootでログインするように設定する
- コンテナ内の参照するフォルダ(と中身のファイル)の権限をappユーザーが開けるように変更する
今回はコンテナ内の/{workdir}/log
(ここではmyapp/log)の権限を変更して対応していきます。
まずは実際に権限がrootになっているのか確認します。
いまのままではコンテナが落ちてしまうのでdocker-compose.yml
のrailsコンテナのコマンドを以下に変更します。
command: sh -c "while :; do sleep 10; done" && bundle exec puma -C config/puma.rb
コンテナの中に入って以下のコマンドを打ちます。
# コンテナの起動
# $ docker-compose up
# コンテナに入る
# $ docker exec -it rails sh
# 権限の確認
$ ls -la
# rootになっている
drwxr-xrwx 2 root root 4096 Jan 16 08:49 log
この権限をapp
に変更できれば良さそうです。
こちらの記事によると、Volumesで設定するとroot
になってしまうようです。
ここで2つの回避策を考えましたので紹介します。
回避策1 コンテナの中で権限を変更する
コンテナの中で直接権限を変更します。
注意としてはroot
でコンテナに入る必要があるので、docker-compose.ymlのuser
をコメントにします。
rootであれば権限問題が起きないのでコンテナは立ち上がるかと思います。
# 別ターミナルを開く
# コンテナに入る
$ docker exec -it rails sh
# 権限を変更する
$ chown app:app -R /myapp/log
chownでエラーが出る場合はroot
ユーザーになっていないのが原因かと思いますのでid -u -n
コマンドで確認してください。
一度変更すれば以降は変更された状態になります。
しかし、ボリュームを削除したり、gitからダウンロードした場合にrootでアクセスしてdocker-compose.ymlを編集してuser:appに変更するという手間も発生するので微妙かなとは思いました。
回避策2 Nginxで権限変更のシェルを実行する
1の欠点を補うためにNginxコンテナを起動するときにシェルを走らせることで回避することにしました。
まず準備としてdocker-compose.yml
のNginxのボリュームを変更します。
web:
build:
context: containers/nginx
volumes:
- public-data:/myapp/public
- tmp-data:/myapp/tmp
- log-data:/myapp/log
ports:
- 80:80
depends_on:
- rails
myapp/log
をvolumesに設定しました。
そして、Dockerfile
でentrypoint.sh
を設定できるようにします。
FROM nginx:alpine
# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*
RUN adduser -D app -u ${UID:-1000}
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
# Nginxの設定ファイルをコンテナにコピー
ADD nginx.conf /etc/nginx/conf.d/myapp.conf
# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
次にnginxのDockerfileと同じディレクトリにentrypoint.sh
を作成します。
# !/bin/sh
set -e
chown app:app -R /myapp/log
exec "$@"
ここまでの設定を行うことで、Nginxコンテナが立ち上がったときにentrypoint.sh
が実行され、volumesで設定されているmyapp/logの権限をappに変更します。また同じユーザーをDockerfileで作成することも忘れないようにしてください。
nginx自体はrootユーザーで起動するため権限変更chown
が利用でき変更が可能です。
railsコンテナで同じように権限を変更しようとなるとroot
でアクセスする必要があるため今回はnginxで権限を変更するように設定しました。
回避策3 マウントに権限を付与する
マウントで権限を与える方法が分かりました。
tmpfs
マウントを使うと権限を与えることが可能になります。
今回はコンテナ間で共有しないのでこれで良さそうです。
tmpfs:
- /myapp/log:exec,mode=777,uid=1000,gid=1000
解決策
解決策はいたってシンプルでDockerfileでユーザーを追加して、ディレクトリコピーの時にchown
すればOKでした。
FROM ruby:alpine3.13
ARG UID
RUN adduser -D app -u ${UID:-1000} && \ # user追加
apk update \
&& apk add --no-cache gcc make libc-dev g++ mariadb-dev tzdata nodejs~=14 yarn
WORKDIR /myapp
COPY Gemfile .
COPY Gemfile.lock .
RUN bundle install
COPY --chown=app:app . /myapp # 権限を付与してコピー
RUN chown app:app -R /myapp/node_modules/.bin
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
# EXPOSE 3000
# CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
USER app # ユーザー変更
RUN mkdir -p tmp/sockets # フォルダ作成はユーザー設定してから
RUN mkdir -p tmp/pids
おわりに
権限問題が複雑に絡まっており解決するまで時間がかかりました。
調べてもよくわからず自分なりに工夫をして解決することができました。