LoginSignup
8
12

More than 1 year has passed since last update.

docker-laravel の Docker 構成をちゃんと理解する【Dockerfile 編 - Nginx】

Posted at

@ucan-lab さんの docker-laravel の環境をちゃんと理解するための記事

今回は Dockerfile の Nginx 編です。

docker-compose 編
Dockerfile 編 - PHP

Dockerfile

dockerfile
# --- ① ビルドイメージの指定 ---
FROM node:16-alpine as node
FROM nginx:1.20-alpine
# ------
# ② メタデータを追加
LABEL maintainer="ucan-lab <yes@u-can.pro>" 
# ③ デフォルトのシェルをオーバーライド
SHELL ["/bin/ash", "-oeux", "pipefail", "-c"]
# ④ 環境変数を設定
ENV TZ=UTC
# ⑤ イメージ上に任意のコマンドを実行
RUN apk update && \
  apk add --update --no-cache --virtual=.build-dependencies g++

# --- ⑥ COPY 命令 ---
# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt
# ------

# ⑦ Nginx の設定ファイルをコピー
# nginx config file
COPY ./infra/docker/nginx/*.conf /etc/nginx/conf.d/

# ⑧ 作業ディレクトリを指定
WORKDIR /work/backend

Dockerfile のコマンド については Dockerfile 編 - PHP を参照

① ビルドイメージの指定

  • Dockerfile 内に複数の FROM 命令をかける
  • 各 FROM 命令から新しいビルドステージが開始される
  • イメージ内に生成された内容、一方から他方にコピーすることができる
  • PHP の Dockerfile で Composer をインストールする時にもマルチステージビルドを使っている

FROM node:16-alpine as node

  • Docker Hub から node のイメージを持ってきている
  • as 構文でビルドステージに名前をつけている
  • 別のビルドステージで使用することができる
  • 名前をつけただけでこのビルドステージは終了
  • alpine は Linux ディストリビューション

nginx:1.20-alpine

② メタデータを追加

LABEL maintainer="ucan-lab <yes@u-can.pro>"

③ デフォルトのシェルをオーバーライド

SHELL ["/bin/ash", "-oeux", "pipefail", "-c"]

  • PHP の Dockerfile と同じ

  • デフォルトのシェルを今回は bash じゃなくて、ash に変更している

    • alpine のイメージは ash を使ってるらしい

④ 環境変数を設定

ENV TZ=UTC

  • OS(Debian) のタイムゾーンを UTC に設定している

⑤ イメージ上に任意のコマンドを実行

RUN apk update && \
apk add --update --no-cache --virtual=.build-dependencies g++

apk update

apk add --update --no-cache --virtual=.build-dependencies g++

  • apk add g++

    • g++ をインストールしている
    • https://pkgs.alpinelinux.org/package/v3.4/main/x86/g++
    • GNU C++ standard library and compiler
    • ちょっと何のためにあるのかわかってない
      • 試しに、丸っと削除削除して動かしてみたけど、 make test までは通った
  • --update

    • apk --updateという引数はない らしい
      • 要するに --update-cache の省略形 らしい
    • --update-cache
      • 他の apk コマンドの前にまず apk update を実行するのと同じ効果が得られる
    • 前の apk update コマンドはなくてもいいのかも?
  • --no-cache

    • ダウンロードされたパッケージは /var/cache/apk にキャッシュされるらしい
    • 不要なキャッシュでイメージのサイズが大きくならないようにしている
  • --virtual=.build-dependencies

    • インストールした複数のパッケージと依存関係を一つの仮想パッケージとして名前をつけて保存
    • 今回は、.build-dependencies って名前をつけている
    • パッケージを削除するときに名前単位で削除したりできるらしい
    • 今回は g++ だけだからなくてもいいのかも?

⑥ COPY 命令

# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt

本家

node のベースイメージから node と npm と yarn をコピーしています。
もし npm, yarn どちらか使用したくない場合は削除して使用してokです。

  • View や React で画面を作った際に、動作するのが Nginx 側だから node を Nginx にコピーしてきてると思われる
    • docker-compose 編
      • PHP は unix ソケットを使って、 PHP コンテナで動く、静的コンテンツは Nginx コンテナで動く

⑦ Nginx の設定ファイルをコピー

COPY ./infra/docker/nginx/*.conf /etc/nginx/conf.d/

  • ./infra/docker/nginx/default.confをコピーしている

  • nginxの設定は、/etc/nginx ディレクトリの nginx.conf に記載される
  • nginx.confに記述してある、 include /etc/nginx/default.d/*.conf; という記述で、conf.d ディレクトリ内の拡張子がconfであるファイルを読み込んでいる
  • /etc/nginx/conf.d/default.conf にサーバーの設定を記載するのが一般的

  • 設定ファイルは別途確認

Nginx コンテナ内の /etc/nginx/nginx.confは下記のような記載になっている。
include /etc/nginx/conf.d/*.conf; でコピーした設定ファイルが読み込まれるようになっている。

nginx.conf
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

⑧ 作業ディレクトリを指定

WORKDIR /work/backend

  • PHP 編と同じ
    • ここではディレクトリが生成されるだけのはず

Nginx の設定ファイル

Laravel の公式ドキュメントを参考にしているらしい

default.conf
access_log /dev/stdout main; # ① アクセスログの設定
error_log /dev/stderr warn; # ② エラーログの設定

server { # 以下、server コンテキスト
    listen 80; # ③ リクエストを受け付けるポート番号を指定
    root /work/backend/public; # ④ ドキュメントルートを設定

    # ⑤ レスポンスヘッダに値を追加
    add_header X-Frame-Options "SAMEORIGIN"; 
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php; # ⑥ インデックスとして使用されるファイルを指定

    charset utf-8; # ⑦ Content-Type を utf-8 に設定

    location / { # ⑧ アクセス URI にファイルがなければ index.php へ遷移させる
        try_files $uri $uri/ /index.php?$query_string;
    }

    # ⑨ アクセスログとエラーログが出ないように設定
    location = /favicon.ico { access_log off; log_not_found off; } 
    location = /robots.txt  { access_log off; log_not_found off; }
    # ⑩ 404 エラーの場合は index.php に遷移する
    error_page 404 /index.php;
    # ⑪ .php にアクセスした場合の設定
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
    # ⑫ well-known 以外のドットファイルへのアクセスを全て拒否
    location ~ /\.(?!well-known).* {
        deny all;
    }
}

nginx の設定ファイルについ概要だけ確認

  • http{ } とか server{ } はディレクティブ(命令)
  • ブロック{ }内の記述は、そのブロック内でのみ有効。その範囲をコンテキストという
  • ディレクティブによって、どのコンテキストで使えるかが決まっている
    • serverディレクティブはhttpコンテキスト内でしか使えない
  • どのブロックにも囲まれていないところはmainコンテキスト

ホストの./infra/docker/nginx/default.confが実行される場所

  • Nginx の Dockerfile でホストの ./infra/docker/nginx/default.conf をコンテナ内の /etc/nginx/conf.d/ ディレクトリ内にコピー

  • コピーしたファイルはコンテナ内の Nginx の設定ファイル /etc/nginx/nginx.conf の http コンテキスト内から include されている

  • つまり、./infra/docker/nginx/default.conf には http コンテキスト内で実行されるディレクティブが書かれている

① アクセスログの設定

access_log /dev/stdout main;

  • 指定したログフォーマットでアクセスログを書き出してくれる
  • アクセスログのフォーマット

    • access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
  • path が /dev/stdout になっているので、ここに access_log が出力されているはず

~ # ls -la /dev/
drwxr-xr-x    5 root     root           340 Dec 30 06:05 .
drwxr-xr-x    1 root     root          4096 Dec 30 06:05 ..
lrwxrwxrwx    1 root     root            11 Dec 30 06:05 core -> /proc/kcore
lrwxrwxrwx    1 root     root            13 Dec 30 06:05 fd -> /proc/self/fd
crw-rw-rw-    1 root     root        1,   7 Dec 30 06:05 full
drwxrwxrwt    2 root     root            40 Dec 30 06:05 mqueue
crw-rw-rw-    1 root     root        1,   3 Dec 30 06:05 null
lrwxrwxrwx    1 root     root             8 Dec 30 06:05 ptmx -> pts/ptmx
drwxr-xr-x    2 root     root             0 Dec 30 06:05 pts
crw-rw-rw-    1 root     root        1,   8 Dec 30 06:05 random
drwxrwxrwt    2 root     root            40 Dec 30 06:05 shm
lrwxrwxrwx    1 root     root            15 Dec 30 06:05 stderr -> /proc/self/fd/2
lrwxrwxrwx    1 root     root            15 Dec 30 06:05 stdin -> /proc/self/fd/0
lrwxrwxrwx    1 root     root            15 Dec 30 06:05 stdout -> /proc/self/fd/1
crw-rw-rw-    1 root     root        5,   0 Dec 30 06:05 tty
crw-rw-rw-    1 root     root        1,   9 Dec 30 06:05 urandom
crw-rw-rw-    1 root     root        1,   5 Dec 30 06:05 zero
  • /dev/stdoutはシンボリックリンクになっている
~ # ls -la /proc/self/fd
total 0
dr-x------    2 root     root             0 Dec 30 06:51 .
dr-xr-xr-x    9 root     root             0 Dec 30 06:51 ..
lrwx------    1 root     root            64 Dec 30 06:51 0 -> /dev/pts/0
lrwx------    1 root     root            64 Dec 30 06:51 1 -> /dev/pts/0
lrwx------    1 root     root            64 Dec 30 06:51 2 -> /dev/pts/0
ls: /proc/self/fd/3: cannot read link: No such file or directory
lr-x------    1 root     root            64 Dec 30 06:51 3
~ # ls -la /dev/pts
total 0
drwxr-xr-x    2 root     root             0 Dec 30 06:05 .
drwxr-xr-x    5 root     root           340 Dec 30 06:05 ..
crw--w----    1 root     tty       136,   0 Dec 30 06:52 0
crw-rw-rw-    1 root     root        5,   2 Dec 30 06:52 ptmx
  • シンボリックリンクを辿っていくと デバイスファイルになっていた
  • アクセスログはどうやって確認したらいいんだろ、わからん

  • format は main になってる

    • おそらくコンテナ内の nginx.conf で設定されている main のフォーマットを指定している
nginx.conf
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

② エラーログの設定

error_log /dev/stderr warn;

error_log file [level];

2番目のパラメータはログのレベルを決定し、debug, info, notice, warn, error, crit, alert, emergのいずれかを指定します。上記のログレベルは、深刻度の高い順に記載されています。あるログレベルを設定すると、>指定された、より深刻なログレベルのすべてのメッセージがログに記録されるようになります。例えば、デフォルトレベルのerrorは、error、crit、alert、およびemergメッセージがログされるようにします。このパラメータ
が省略された場合、errorが使用されます。

  • /dev/stderr にエラーログを出力
  • warn レベル以上のログを出力

  • access_log と同様にシンボリックリンクになって、たどるとデバイスファイルになっていた。確認方法がわからん

③ リクエストを受け付けるポート番号を指定

listen 80;

  • listen ディレクティブでリクエストを受け付けるポートや IP アドレスを指定できる
  • 今回は 80 番ポートでリクエストを受け付けるように設定している

docker-compose.yml で nginx 側の 80 番ポートとホストの 80 番ポートをマッピングしていたのはこのため

④ ドキュメントルートを設定

root /work/backend/public;

  • rootディレクティブでドキュメントリュートを指定

  • /work/backenddocker-compose.ymlで Laravel のソースコードをマウントするディレクトリ

  • つまり、 Lravel の public ディレクトリをドキュメントルートに指定している

⑤ レスポンスヘッダに値を追加

レスポンスコードが 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), または 308 (1.13.0) であれば、指定したフィールドを応答ヘッダに追加します。パラメータ値には変数を含めることができる。

X-Frame-Options "SAMEORIGIN";

X-Frame-Options は HTTP のレスポンスヘッダーで、ブラウザーがページを 、

  • SAMEORIGIN - 同じオリジンのフレーム内でのみ表示可能
  • nginx での設定

X-XSS-Protection "1; mode=block";

HTTP の X-XSS-Protection レスポンスヘッダーは Internet Explorer, Chrome, Safari の機能で、反射型クロスサイトスクリプティング (XSS) 攻撃を検出したときに、ページの読み込みを停止するためのものです。

1; mode=block
XSS フィルタリングを有効化します。攻撃を検知すると、ページをサニタイジングするよりも、ページのレンダリングを停止します。

X-Content-Type-Options "nosniff";

X-Content-Type-Options は HTTP のレスポンスヘッダーで、 Content-Type ヘッダーで示された MIME タイプを変更せずに従うべきであることを示すために、サーバーによって使用されるマーカーです。これにより、MIME タイプのスニッフィングを抑止するこ
とができます。言い替えれば、 MIME タイプを意図的に設定することができます。

nosniff
リクエスト先のタイプが style でありその MIME タイプが text/css ではない場合、または、タイプが script で MIME タイプが JavaScript の MIME タイプではない場合にリクエストをブロックします。

⑥ インデックスとして使用されるファイルを指定

index index.html index.htm index.php;

  • incex ディレクティブ

インデックスとして使用されるファイルを定義します。ファイル名には変数を含めることができる。ファイルは指定された順序でチェックされる。

  • 今回は index.html, index.htm, index.php の優先順位で index ファイルが決まる
  • index.html, index.htm ファイルがなければ、Laravel の index.php ファイルがインデックスファイルとして読み込まれる

  • つまり、http://localhost でアクセスした時には index.php が呼ばれる

⑦ Content-Type を utf-8 に設定

charset utf-8;

charset

レスポンスヘッダーフィールド "Content-Type" に、指定された文字セットを追加します。

⑧ アクセス URI にファイルがなければ index.php へ遷移させる

location

  • リクエスト URI に応じた設定を行う
  • 今回は、 / にアクセスがきた時の設定を行っている

  • / で始まる全てのリクエスト URI に対して適応される

try_files $uri $uri/ /index.php?$query_string;

指定された順番でファイルの存在を確認し、最初に見つかったファイルをリクエスト処理に使用する。処理は、現在のコンテキストで実行される。ファイルへのパスは、fileパラメータからrootとaliasディレクティブに従って構築される。また、"$uri/" のよう>に名前の最後にスラッシュを指定することで、ディレクトリの存在を確認することができる。いずれのファイルも見つからなかった場合、最後のパラメータで指定された uri に内部リダイレクトされます。

  • アクセスしてきた URL にファイルがあるか
  • アクセスしてきた URL にディレクトリがあるか
  • いずれもなければ、/index.php$query_string へ遷移する

⑨ アクセスログとエラーログが出ないように設定

location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }

  • /favicon.ico/robots.txt にアクセスされた場合の設定

  • log_not_found

ファイルが見つからない場合のエラーログをerror_logに記録するかどうかを設定します。

  • access_log

    • アクセスログを off にしている

⑩ 404 エラーの場合は index.php に遷移する

error_page

指定されたエラーに対して表示されるURIを定義する。

⑪ .php にアクセスした場合の設定

location ~ \.php$ {
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

location ~ \.php$

  • ~ 正規表現(大文字・小文字を区別する)
  • ~ \.php$ 拡張子が .php のファイルに対して設定を行う

fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;

FastCGI サーバーのアドレスを設定します。
UNIX-domain socket path:
fastcgi_pass unix:/tmp/fastcgi.socket;

  • unix ドメインソケットのパスを指定している
  • docker-compose.yml で unix ドメインソケットをマウントしているので、そこへのパスを指定する
  • おそらくこれで、.php ファイルにアクセスした時は php コンテナと通信をして、 PHP コンテナの FastCGI で PHP を実行することができる

fastcgi_index index.php;

スラッシュで終わる URI の後に付加されるファイル名を $fastcgi_script_name 変数の値に設定します。

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;

FastCGI サーバーに渡すべきパラメータを設定します。値にはテキスト、変数、およびそれらの組み合わせを含めることができます。

PHPでは、SCRIPT_FILENAMEパラメータはスクリプト名を決定するために使用され、QUERY_STRINGパラメータはリクエストパラメータを渡すために使用されます。

  • FastCGI サーバー(PHP コンテナ)に SCRIPT_FILENAME を渡している

    • SCRIPT_FILENAME パラメータは、 PHP FPM に渡す必要があるらしい
  • $realpath_root

現在のリクエストに対する root または alias ディレクティブの値に対応する絶対パス名。 すべてのシンボリックリンクは実際のパスに解決されます。

  • とりあえず、ベタがきでも index.php のパスを指定すればいいらしい

なんで必要かまではわかっていない。

include fastcgi_params;

  • fastcgi_params を include している
    • PHP の $_SERVER 内に値が入る

⑫ well-known 以外のドットファイルへのアクセスを全て拒否

location ~ /\.(?!well-known).* {
        deny all;
    }

指定されたネットワークまたはアドレスのアクセスを拒否する

8
12
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
8
12