Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
149
Help us understand the problem. What is going on with this article?
@minato-naka

【Docker Compose】設定内容を1行ずつ理解しながらLaravel環境構築(PHP-FPM、Nginx、MySQL、Redis)

はじめに

この記事は、Docker入門シリーズ記事3本の3本目です。
Dockerで環境構築するための最低限の概念理解
Dockerで環境構築するための最低限のコマンドを一通り実践する
【Docker Compose】設定内容を1行ずつ理解しながらLaravel環境構築(PHP-FPM、Nginx、MySQL、Redis)

概要

この記事の目的

この記事の目的は
Docker Composeを利用してLaravelの環境構築をすること
ではなく
Docker Composeの設定内容をちゃんと理解しながらLaravelの環境構築をすること
です。

Docker Composeを利用してLaravelの環境構築方法を解説する記事は多くありますが、
docker-compose.ymlやDockerfile、Nginx設定ファイルの完成形が貼ってあって、
コマンドを実行すれば環境構築完了、
というものが多いです。

Laravelが動く環境がとりあえず欲しいだけの場合はこれで良いですが、
Docker Composeの勉強を目的としている場合には
解説が不十分だと思ったので、
自分の復習を兼ねて記事にしました。

また、前回の記事でやった一通りのDocker環境構築フロー、
ポート公開、マウントなどの基本概念は理解している前提で進めます。

構築する環境

今回構築するコンテナは4つです。
1. Nginxコンテナ
2. PHP-FPMコンテナ
3. MySQLコンテナ
4. Redisコンテナ

そして、PHP-FPMコンテナには
Laravel公式のサンプルアプリプログラム完成形をそのまま置いて、
実際にアプリが動作するようにします。
https://github.com/laravel/quickstart-intermediate

このアプリは、
ユーザ登録機能、ログイン機能、タスク登録削除機能がある
シンプルなタスク管理アプリです。

DBへのデータ登録やログイン機能があるので、
MySQLコンテナへのデータ保存や
Redisコンテナへのセッション保存を
実際にアプリを通して利用することができます。

環境構築フロー

今回はこのような流れでLaravel環境構築を進めます。

  1. Nginxコンテナ作成
  2. PHP-FPMコンテナ作成
  3. NginxコンテナとPHP-FPMコンテナ間の通信設定
  4. Laravelサンプルアプリを導入
  5. MySQLコンテナ作成
  6. Redisコンテナ作成

ディレクトリ構成

今回は、ホストPCにlaravel-sampleというディレクトリを作成し、
その中にdocker-compose.ymlや各コンテナの設定ファイル、
Laravelサンプルアプリのソースを配置していきます。

最終的にはこのようなディレクトリ構成になります。

laravel-sample/
      ├─ docker-compose.yml
      ├─ nginx/
      │  └─ default.conf
      ├─ php-fpm/
      │  └─ Dockerfile
      ├─ mysql/
      │  └─ data/
      └─ source/
         └─ app/
         └─ bootstrap/
         └─ ...etc

一番上のlaravel-sampleのディレクトリだけ作成し、
次の作業に進んでください。

Nginxコンテナ作成

それでは、さっそくNginxコンテナを作成します。

まずは、laravel-sampleディレクトリの下に、
docker-compose.ymlファイルを作成します。

laravel-sample/
      └─ docker-compose.yml

docker-compose.ymlにこのように記述します。

docker-compose.yml
version: '3'
services:
    nginx:
        image: nginx:1.15
        ports:
            - 80:80

version: '3'
これはDocker Composeのバージョンです。
現在の最新バージョンである3にします。

services:
servicesには、作成するコンテナを定義します。
現在はNginxコンテナのみ記述していますが、
このあとPHP-FPM、MySQL、Redisのコンテナ定義がここに追記されていきます。

nginx:
この下にNginxコンテナのオプションを色々記述していきます。

image: nginx:1.15
Nginxコンテナのもとになるイメージをここで指定しています。
今回はDocker Hubに公式で用意されているNginxイメージを利用します。
バージョンは、現在最新である1.15にします。

ports
これは前回の記事で実践した-pコマンドと同じです。
ホスト側の80番ポートとコンテナ側の80番ポートをつなげる
という設定になります。

このオプションをつけていないと
ホストPCのブラウザからNginxコンテナにアクセスすることができません。

これでとりあえず
Nginxコンテナを作成するための最低限の設定が完了しているので、
実際にコンテナを作成します。

laravel-sampleディレクトリでターミナルを開き、
このコマンドを実行します。

docker-compose up -d

docker-compose updocker-compose.ymlの設定に従い
コンテナを作成、起動するコマンドです。

-dは、dockerをバックグラウンド実行するためのオプションです。

docker ps -aでコンテナを確認します。

CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                NAMES
5288fd7aeea5        nginx:1.15               "nginx -g 'daemon of…"   12 seconds ago      Up 11 seconds       0.0.0.0:80->80/tcp   laravel-sample_nginx_1

このように表示されれば正しくコンテナ作成できています。

ホストPCのブラウザから
http://localhost
にアクセスします。

このようにNginxのウェルカムページが表示されれば
正しくコンテナのポート公開ができています。
キャプチャ.PNG

ホスト側のディレクトリをコンテナ側にマウントする

次に、前回の記事で-vオプションを使ってやったように、
ホストPCに置いた
・プログラムソースファイル
・サーバ設定ファイル
をコンテナ側にマウントします。

このようにnginxフォルダにdefault.confを、
sourceフォルダにindex.htmlを置きます。

laravel-sample/
      ├─ docker-compose.yml
      ├─ nginx/
      │  └─ default.conf
      └─ source/
         └─ index.html

index.htmlの内容はなんでもいいので
適当に記述しておきます。

default.confの内容は、コンテナの中にある
/etc/nginx/conf.d/default.conf
をコピーしてください。

default.confの内容
default.conf
server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

そして、マウントするためのオプションを
docker-compose.ymlに追記します。

docker-compose.yml
version: '3'
services:
    nginx:
        image: nginx:1.15
        ports:
            - 80:80
        volumes:
            - ./source:/usr/share/nginx/html
            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf

これでコンテナの削除、再作成を行ってください。

#コンテナ削除
docker-compose down

#コンテナ作成
docker-compose up -d

これで http://localhost にアクセスします。

先ほどホスト側で作成したindex.htmlの内容が表示されれば
正しくマウントできています。

さらにホスト側でindex.htmlを更新して
それがちゃんと反映されるか確認してください。

これでNginxコンテナの作成は完了です。

PHP-FPMコンテナ作成

次に、PHP-FPMのコンテナを作成します。
Nginxコンテナは既存イメージをそのまま利用しましたが、
PHP-FPMコンテナはDockerfileからイメージをbuildし、コンテナを作成します。

Dockerfile作成

ホストPCにphp-fpmというフォルダを作り、
そこにDockerfileを置きます。

laravel-sample/
      ├─ docker-compose.yml
      ├─ nginx/
      │  └─ default.conf
      ├─ php-fpm/
      │  └─ Dockerfile
      └─ source/
         └─ index.html

Dockerfileの内容はこのようにします。

FROM php:7.2-fpm

FROMにphp:7.2-fpmのイメージを指定しているだけです。
後々Laravelを動かすための設定を色々と追記しますが、
今はこれだけでいいです。

次にdocker-compose.ymlにPHP-FPMコンテナの情報を追記します。

docker-compose.yml
version: '3'
services:
    nginx:
        image: nginx:1.15
        ports:
            - 80:80
        volumes:
            - ./source:/usr/share/nginx/html
            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    php-fpm:
        build: ./php-fpm
        volumes:
            - ./source:/var/www/html

php-fpm
サービス一覧にphp-fpmを追加します。

build: ./php-fpm
このコンテナの元になるDockerfileがおいてあるパスを指定しています。

volumes:
PHP-FPMコンテナでもホストPC側のプログラムソースディレクトリをマウントしておきます。

それではコンテナを作成します。
Dockerfileから作成する場合には、--buildオプションを追加してコマンド実行します。

docker-compose up -d --build

docker ps -aで確認してみます。

CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                NAMES
5288fd7aeea5        nginx:1.15               "nginx -g 'daemon of…"   12 seconds ago      Up 11 seconds       0.0.0.0:80->80/tcp   laravel-sample_nginx_1
e823a579a920        laravel-sample_php-fpm   "docker-php-entrypoi…"   12 seconds ago      Up 11 seconds       9000/tcp             laravel-sample_php-fpm_1

正しくコンテナ起動できています。

コンテナにログインして
/var/www/htmlにホスト側のindex.htmlが
マウントできていることを確認してください。

#コンテナにログイン
docker exec -it laravel-sample_php-fpm_1 bash

また、このコンテナ内で正しくPHPが動作するかどうか確認してみます。
まず、ホスト側にあるindex.htmlを削除し、
index.phpを置きます。

laravel-sample/
      ├─ docker-compose.yml
      ├─ nginx/
      │  └─ default.conf
      ├─ php-fpm/
      │  └─ Dockerfile
      └─ source/
         └─ index.php  ←これを追加

ファイル内容は、このように簡単なPHPプログラムを書きます。

index.php
<?php
    echo(123456789);
    echo(PHP_EOL);

これでまたコンテナにログインし、
php index.phpコマンドを実行してPHPが動作するか確認します。

#コンテナにログインしている状態
php index.php
123456789

「1234567879」が出力されたので、正しくPHPが動作していることが確認できました。

NginxコンテナとPHP-FPMコンテナ間の通信設定

次に、
ブラウザからhttp://localhost にアクセスしたら
先ほどのindex.phpが実行され
「123456789」が画面に表示されるように設定をします。

いまhttp://localhost にアクセスするとNginxの403ページが表示されます。
キャプチャ.PNG

原因はこの2つです。
1. 「/」で終わるURLでアクセスされた時の表示ページとしてindex.phpが設定されていない
2. 「*.php」のファイルにアクセスされた時にPHP-FPMと通信してPHPを実行させるための設定がされていない

Nginx設定ファイルdefault.confを修正することでこれを解決します。

まずはこれの修正。

「/」で終わるURLでアクセスされた時の表示ページとしてindex.phpが設定されていない

default.confのこの部分にindex.phpを追加します。

default.conf
location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm index.php;
                                   #↑これを追加
}

次にこれの修正

「*.php」のファイルにアクセスされた時にPHP-FPMと通信してPHPを実行させるための設定がされていない

default.confの「pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000」
と書いてある部分をこのように修正します。

default.conf
#変更前
#location ~ \.php$ {
#    root           html;
#    fastcgi_pass   127.0.0.1:9000;
#    fastcgi_index  index.php;
#    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
#    include        fastcgi_params;
#}

#↓↓↓↓↓↓↓↓変更↓↓↓↓↓↓↓↓

location ~ \.php$ {
    root           /var/www/html;
    fastcgi_pass   php-fpm:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
}

これでNginxコンテナに対してphpファイルにアクセスが来た際に、
PHP-FPMコンテナと通信してPHPを実行する設定ができました。

設定を反映するためにコンテナを再起動します。

docker-compose stop
docker-compose start

http://localhost にアクセスして
画面に「123456789」が表示されれば
Nginx経由でPHPが正しく実行できている状態です。
キャプチャ.PNG

Laravelを導入

PHPが動くようになりましたが、
まだLaravelを動かすために必要な設定が色々あります。

Laravelサンプルアプリの画面を実際に表示できるところまで
設定します。

Laravelサンプルアプリソースをcloneする

まずはLaravelサンプルアプリのソースをcloneしてきます。

先ほどのindex.phpは削除して、
/sourceのディレクトリでこのコマンドを実行します。

git clone https://github.com/laravel/quickstart-intermediate.git .

これで現在このようなディレクトリ構成になっています。

laravel-sample/
      ├─ docker-compose.yml
      ├─ nginx/
      │  └─ default.conf
      ├─ php-fpm/
      │  └─ Dockerfile
      └─ source/
         └─ app/
         └─ bootstrap/
         └─ ...etc

※...etcの部分は省略していますがLaravelのソースが色々置いてあります。

PHP-FPMコンテナに、Laravelに必要なライブラリ、ツールをインストール

Laravelが動作するためにサーバにインストールする必要のあるものがいくつかあるので、
PHP-FPMのDockerfileをこのように修正します。

FROM php:7.2-fpm

#pdoインストール
RUN docker-php-ext-install pdo_mysql

#composerインストール
RUN curl -sS https://getcomposer.org/installer | php
RUN mv composer.phar /usr/local/bin/composer

#gitインストール
RUN apt-get update
RUN apt-get install -y git

これでもう一度buildし直してコンテナを作ります。

docker-compose up -d --build

composer installを実行する

次に、PHP-FPMコンテナでcomposer installコマンドを実行します。

#PHP-FPMコンテナにログイン
docker exec -it laravel-sample_php-fpm_1 bash

#コンテナにログインしている状態
#composer install実行
composer install

この結果、souce/フォルダの下に
vendor/というフォルダが追加されているはずです。

NginxコンテナとPHP-FPMコンテナのドキュメントルートを修正

Laravelの仕様で、
ドキュメントルートはプロジェクトのルートディレクトリではなく、
1つ下の階層にあるpublicというディレクトリにする必要があります。

Nginxのdefault.confファイルの
下記2か所(「publicを追加」と書いてある箇所)を修正します。

dafault.conf
location / {
    root   /usr/share/nginx/html/public;
                                 #↑publicを追加
    index  index.html index.htm index.php;
}

...

location ~ \.php$ {
    root           /var/www/html/public;
                                 #↑publicを追加
    fastcgi_pass   php-fpm:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
}

この設定を反映させるためにコンテナを再起動します。

docker-compose stop
docker-compose start

これでhttp://localhost にアクセスしてみます。

このLaravelサンプルアプリの画面が表示されれば
Laravel動作のための設定が正しくできています。
キャプチャ.PNG

ルートディレクトリ以外が404になる現象を解消

今の状態でLaravelサンプルアプリの画面右上の「Register」ボタンなどをクリックし
画面遷移すると
404ページが表示されます。

原因は、Nginxのルーティングのために必要な設定が抜けているからです。
dafault.confにこの設定を追加します。

default.conf
location / {
    root   /usr/share/nginx/html/public;
    index  index.html index.htm index.php;

    try_files $uri $uri/ /index.php$is_args$args;
    ###↑この行を追加
}

これでもう一度コンテナ再起動。

docker-compose stop
docker-compose start

これで画面遷移しても404ページにならないようにできました。

この状態で右上の「Register」ボタンからユーザ登録操作をしてみると、
このようにDB接続エラーになると思います。
キャプチャ.PNG

当然ですが、今は接続先のDBが存在していないためこのようなエラーが発生します。
次にMySQLコンテナを作成してこれを解決します。

MySQLコンテナ作成

docker-compose.ymlにMySQLの設定を追記します。

docker-compose.yml
version: '3'
services:
    nginx:
        image: nginx:1.15
        ports:
            - 80:80
        volumes:
            - ./source:/usr/share/nginx/html
            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    php-fpm:
        build: ./php-fpm
        volumes:
            - ./source:/var/www/html
    mysql:
        image: mysql:5.7
        environment:
            MYSQL_DATABASE: laravel_sample_db
            MYSQL_ROOT_PASSWORD: password
            MYSQL_USER: laravel_sample_user
            MYSQL_PASSWORD: password
            TZ: Asia/Tokyo
        ports:
            - 3306:3306

image: mysql:5.7
MySQLコンテナは公式のこのイメージをそのまま使います。

environment:
これはMySQLコンテナ作成時に初期作成される
データベースの名前やユーザ、パスワードの設定を書いています。

ports:
ホスト側3306番ポートとコンテナ側3306番ポートをつなげる設定です。

コンテナのポートをホスト側に公開しなくても
Laravelサンプルアプリは動作しますが、
ホストPCからMySQL WorkbenchやA5:SQL Mk-2などのツールを使って
接続したい場合にはコンテナのポートをこのように公開する必要があります。

これでMySQLコンテナを作成します。

docker-compose up -d

MySQLコンテナに入って確認してみます。

#コンテナにログイン
docker exec -it laravel-sample_mysql_1 bash

#コンテナにログインしている状態
#コンテナのMySQLにログイン
mysql -u laravel_sample_user -p
password

#MySQLにログインしている状態
#データベース一覧表示
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel_sample_db  |
+--------------------+
2 rows in set (0.01 sec)

docker-compose.ymlに書いた通りの
ユーザ、パスワードでログインでき、
「laravel_sample_db」が存在することが確認できました。

Laravelのenvファイル修正

LaravelサンプルアプリからこのMySQLコンテナに接続するためには
DB情報をLaravelの.envに記述する必要があります。

sourceディレクトリ配下にある.envファイルの
DB情報をこのように変更します。

DB_HOST=mysql
DB_DATABASE=laravel_sample_db 
DB_USERNAME=laravel_sample_user
DB_PASSWORD=password

これでLaravelからDBにアクセスできるはずなので、
migration実行してみます。

#PHP-FPMコンテナにログイン
docker exec -it laravel-sample_php-fpm_1 bash

#コンテナにログインしている状態
#migration実行
php artisan migrate

これでアプリに必要なテーブルも作成できたので、
実際にアプリを動かしてみます。

キャプチャ.PNG

ユーザ登録やタスク登録など正しく動作しています。

コンテナのポートを公開しているので、
好きなDBクライアントツールを使って
localhostの3306番ポートに接続すれば
GUIでのデータ確認、更新もできます。

MySQLのデータ永続化

今の状態でこのMySQLコンテナを削除し、再作成すると
先ほど登録したデータは消えてしまいます。
(テーブル自体が消えます)

これを解消するために、
プログラムソースファイルと同じように
ホスト側のディレクトリをMySQLコンテナのデータ置き場にマウントし、
コンテナを削除してもデータが消えないようにします。

docker-compose.ymlにこのvolumesオプションを追記するだけです。

docker-compose.yml
version: '3'
services:
    nginx:
        image: nginx:1.15
        ports:
            - 80:80
        volumes:
            - ./source:/usr/share/nginx/html
            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    php-fpm:
        build: ./php-fpm
        volumes:
            - ./source:/var/www/html
    mysql:
        image: mysql:5.7
        environment:
            MYSQL_DATABASE: laravel_sample_db
            MYSQL_ROOT_PASSWORD: password
            MYSQL_USER: laravel_sample_user
            MYSQL_PASSWORD: password
            TZ: Asia/Tokyo
        ports:
            - 3306:3306
        volumes:
            - ./mysql/data:/var/lib/mysql

これでコンテナを削除し再作成すれば、
MySQLのデータが永続化できる状態になります。

docker-compose down
docker-compose up -d

ホスト側には自動で/mysql/dataのディレクトリが作られます。

laravel-sample/
      ├─ docker-compose.yml
      ├─ nginx/
      │  └─ default.conf
      ├─ php-fpm/
      │  └─ Dockerfile
      ├─ mysql/
      │  └─ data/
      └─ source/
         └─ app/
         └─ bootstrap/
         └─ ...etc

もう一度migrationを実行し、
データをいろいろ登録してから
コンテナ削除、再作成をしてデータが消えないことを確認してみてください。

Redisコンテナ作成

これまでの作業で、Laravelサンプルアプリが
動作するために必要な作業はすべて完了していますが、
最後におまけでRedisコンテナを作成し、
キャッシュ保存先をRedisにしてみます。

docker-compose.ymlにRedisの設定を追記します。

docker-compose.yml
version: '3'
services:
    nginx:
        image: nginx:1.15
        ports:
            - 80:80
        volumes:
            - ./source:/usr/share/nginx/html
            - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    php-fpm:
        build: ./php-fpm
        volumes:
            - ./source:/var/www/html
    mysql:
        image: mysql:5.7
        environment:
            MYSQL_DATABASE: laravel_sample_db
            MYSQL_ROOT_PASSWORD: password
            MYSQL_USER: laravel_sample_user
            MYSQL_PASSWORD: password
            TZ: Asia/Tokyo
        ports:
            - 3306:3306
        volumes:
            - ./mysql/data:/var/lib/mysql
    redis:
        image: redis:latest
        ports:
            - 6379:6379

image: redis:latest
公式のredisイメージ最新をそのまま利用します。

ports:
これもMySQLコンテナを作成したときと同じで、
アプリを動作させる分にはRedisコンテナのポートを公開する必要はありません。

Medisなど、Redisに接続してGUIでデータを確認、操作するツールを利用したい場合は
このようにポートを公開してください。

これでコンテナ作成します。

docker-compose up -d

LaravelのRedis利用設定

Redisコンテナ自体は作成できましたが、
Laravel側でRedisを利用するために必要な設定がいくつかあるのでしていきます。

まず、composerを使ってpredisというライブラリをインストールします。

#PHP-FPMコンテナにログイン
docker exec -it laravel-sample_php-fpm_1 bash

#コンテナにログインしている状態
#predisインストール
composer require predis/predis

次にLaravelの.envの設定を変更します。
このように下記箇所を変更してください。

 .env
1
CACHE_DRIVER=file
↓
CACHE_DRIVER=redis

2
SESSION_DRIVER=file
↓
SESSION_DRIVER=redis

3
REDIS_HOST=127.0.0.1
↓
REDIS_HOST=redis

これでRedisを使うための設定はすべて完了です。

Laravelサンプルアプリを操作し、
ログインやログアウト操作を正常に行えれば問題ないです。

本当にこれでセッション保存先としてRedisがちゃんと使われているのかわからないので、
先ほど言ったようにMedisなどのツールを使ってlocalhostの6379番ポートに接続してみるといいです。

実際にデータが入っていることが確認できます。

最後に

これで、
1. Nginxコンテナ
2. PHP-FPMコンテナ
3. MySQLコンテナ
4. Redisコンテナ
のコンテナをDocker Composeで作成し、
実際にLaravelアプリが動作する環境が構築できました。

実際にチームで開発を進める際には、
・docker-compose.yml
・各コンテナのDockerfile
・各コンテナのサーバ設定ファイル
・プログラムソースファイル
を共有しながら進めるといいです。

Dockerの概念理解
コンテナ単体での基本操作実践
コンテナ複数を管理するDocker Compose実践
と進めてきましたが、
機会があれば
複数サーバ上で複数コンテナを管理するためのツールである
KubernetesやDocker Swarmも実践記事を書ければと思います。
(最後まで見ていただいた方、最初の記事からLGTMしてもらえるとうれしいです)

149
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
149
Help us understand the problem. What is going on with this article?