Dockerでlaravel6.xのローカル環境構築(nginx, node, php, mysql)

Vagrantよりも軽量で、「any Application Anywhere」と言う通り、どのHardwareでも構築できるDockerでLaravel環境を構築したいと思います
※本当の動機は、Web系エンジニアであればDockerは使えて常識という空気感がある為。。

## 前準備
// OSはdockerが開発されているubuntu
$ uname -a
Linux vagrant-ubuntu-trusty-64 4.4.0-148-generic #174~14.04.1-Ubuntu SMP Thu May 9 08:17:37 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
// docker ceインストール済み
$ docker version
Version: 18.06.3-ce
// docker-compose インストール済み
$ docker-compose version
docker-compose version 1.21.2, build a133471
// git インストール済

▽UbuntuへのDockerインストール手順 ※trustyの場合、kernelを4.4に上げる必要あり
https://docs.docker.com/install/linux/docker-ce/ubuntu/
▽docker composeのインストール
$ sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

### 全体構成
構成は、currentに(1)docker-compose.ymlがあり、その下に、各コンテナ(2)web(nginx:1.13.5-alpine)、(3)app(node:12.13.0-alpine + FROM php:7.3-fpm-alpine)、(4)db(mysql:5.7.19)のフォルダと、(5)laravelのソースコードがある。

なぜapacheではなく、nginxを使っているかというと、php:*-fpm-alpineにbashでログインしてyum install httpd後に、systemctl start httpdしようとするとエラーになるため、enginxの方が都合がいい為。

また、Laravelはnode.jsは必須の為、nodeとphpのマルチビルドにしている。phpのコンテナを立ち上げて、nodeをインストールすればいいじゃないかと思うかもしれないが、基本的にコンテナ内での操作は最小限にする。

$ tree -L 3
.
├── app
│   └── Dockerfile ・・・(3)
├── data
│   └── ${appName} ・・・(5)
│       ├── app
│       ├── appspec.yml
│       ├── artisan
│       ├── bootstrap
│       ├── composer.json
│       ├── composer.lock
│       ├── composer.phar
│       ├── config
│       ├── database
│       ├── package.json
│       ├── package-lock.json
│       ├── phpunit.xml
│       ├── public
│       ├── README.md
│       ├── resources
│       ├── routes
│       ├── scripts
│       ├── server.php
│       ├── storage
│       ├── tests
│       ├── vendor
│       └── webpack.mix.js
├── db ・・・(4)
│   ├── Dockerfile
│   ├── initial.sql
│   └── my.cnf
├── docker-compose.yml ・・・(1)
└── web
    └── default.conf ・・・(2)

### (1)docker-compose.yml
laravelでのルーティングはpublicフォルダなので、[web](nginx)では、publicフォルダを同期させ、[app](php-alpine)では、アプリディレクトリを同期させる。

version: '3'
services:
  web:
    image: nginx:1.13.5-alpine
    ports:
      - "80:80"
    depends_on:
      - app
    volumes:
      - ./web/default.conf:/etc/nginx/conf.d/default.conf
      - ./data/${appName}/public:/var/www/html

  app:
    build: ./app
    env_file: .env
    environment:
      DATABASE_HOST: db
    depends_on:
      - db
    volumes:
      - ./data/${appName}:/var/www/html/nomad

  db:
    build: ./db
    env_file: .env
    ports:
      - "3306:3306"
    volumes:
      - db-data:/var/lib/mysql
      - ./db/initial.sql:/docker-entrypoint-initdb.d/initial.sql
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

volumes:
  db-data:

### (2)default.config
最後に、locationで「storage|js|css|img」へのアクセスを追記している。これをかかないと、css、js、img、storageが効かないので注意が必要

server {
    listen 80;
    server_name _;

    root  /var/www/html/${appName}/public;
    index index.php;

    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(\.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    location ~ ^/(storage|js|css|img)/.*$ {
        root /var/www/html;
    }
}

### (3)Dockerfile
g++, libstdc++, libgccをインストールしないと、nodeが動かないので注意が必要

FROM node:12.13.0-alpine as node

FROM php:7.3-fpm-alpine

RUN docker-php-ext-install pdo_mysql mysqli mbstring
RUN apk add --no-cache --virtual g++
RUN apk add --no-cache libstdc++ && apk add --no-cache libgcc

WORKDIR /var/www/html

COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules/ /usr/local/lib/node_modules/
RUN ln -s /usr/local/bin/node /usr/local/bin/nodejs \
    && ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
    && ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npx

### (4) db
Docker公式のmysqlは日本語対応していない為、my.confを作ってあげる必要がある。

Dockerfile

FROM mysql:5.7.19

ADD ./my.cnf /etc/mysql/my.cnf

RUN chmod 644 /etc/mysql/my.cnf

my.conf

[mysql]
default-character-set=utf8mb4
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_bin
[client]
default-character-set=utf8mb4

### (5) app
Laravelのプロジェクトをgit clone
.envのDB_HOSTは[db](mysql:5.7.19)のコンテナ名を入力する

### コンテナのbuild
$ sudo docker-compose build
$ sudo docker-compose up -d
$ sudo docker ps

// 立ち上がったら、[app](php-alpine)にログイン
$ sudo docker exec -ti ${containerId} sh
$ cd ${appName}
$ php composer.phar install
$ php composer.phar dump-autoload
$ php artisan key:generate
$ php artisan migrate
$ php artisan storage:link

// tableが作成されたら、[db](mysql:5.7.19)にログイン
$ sudo docker exec -ti ${containerId} bash
$ export LANG=ja_JP.UTF-8
// 省略: mysql の日本語化
$ mysql -u root -p
$ use ${dbName}
$ insert into …

### 動作確認
http://192.168.33.10/login

大体こんな感じか。ansibleでも書けるが、確かにdocker-composeとDockerfileでコード化できるところが面白い。
Vagratで構築するのとは全然異なるので、どちらで開発するかはよく考えた方が良さそう。