Bài viết này sẽ giải quyết các vấn đề:

  • Development / Production server chạy nhiều hơn 1 dự án web.
  • Proxy React SPA dùng subpath instead of subdomain or other port.

Nội dung khá giống với bài viết Setup môi trường dev Django với Docker nhưng giải quyết thêm vài vấn đề đặc thù khi setup php-fpm, node dùng proxy network.

Lý do không dùng mỗi nginx instance cho mỗi dự án: Khi dùng mỗi nginx instance cho mỗi dự án thì chúng ta sẽ gặp vấn đề về port 80. Chúng ta có thể chạy nhiều nginx instance cùng lúc nhưng chỉ có duy nhất 1 port 80. Điều đó có nghĩa là nếu chúng ta có 2 dự án web chạy cùng lúc thì chỉ có 1 dự án dùng được port 80, ví dụ: project1.test và dự án còn lại phải dùng port khác, ví dụ: project2.test:8080. Điều này gây ra rất nhiều bất tiện trên cả môi trường Development lẫn Production.

Để giải quyết vấn đề đó thì ta có thể dùng 1 nginx container dùng chung cho tất cả các dự án. Vấn đề phát sinh khi dùng phương pháp này là các container không dùng chung 1 network nên ta cần tạo 1 proxy network dùng chung cho tất cả các container.

Giả sử trong home của bạn có 1 folder tên là code, folder này chứa tất cả mọi thứ liên quan tới bài viết này.

Bước 1: Tạo proxy network

docker network create common_proxy

Bước 2: Setup Nginx container

Tạo 1 folder cho Nginx container

mkdir ~/code/nginx

Folder nginx có cấu trúc sau:

└── nginx
    ├── conf.d
    │   ├── default.conf
    │   └── project1.conf
    └── docker-compose.yml

Lần lượt các file có nội dung:

version: '2'
# docker-compose.yml
networks:
    proxy:
        external:
            name: common_proxy

services:
    nginx:
        container_name: nginx
        image: "nginx:1.12.1"
        restart: always
        volumes:
            - ./conf.d:/etc/nginx/conf.d
        ports:
            - "80:80"
        networks:
            - proxy
# default.conf
# Default page if no configuration match
server {
    listen 80;
    server_name _;
    charset utf-8;

    location / {
        return 404;
    }
}
# project1.conf
server {
    listen 80; 
    server_name project1.test;
    charset utf-8;
    root /code/public;
    index index.php index.html;

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

    location /api/v1/ {
        try_files $uri /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass project1_api: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 /public {
        try_files $uri $uri/ /index.html;
        rewrite ^/public(/.*)$ $1 break;
        add_header Access-Control-Allow-Origin *;
    }

    location /admin/ {
        proxy_pass http://project1_client:4004;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        rewrite ^/admin(/.*)$ $1 break;
    }

    location /user/ {
        proxy_pass http://project1_client:4004;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        rewrite ^/user(/.*)$ $1 break;
    }
}

Bạn có thể để ý thấy project1_clientproject1_client là 2 service name được khai báo trong container tiếp theo.

Bước 3: Cấu trúc dự án

Giả sử dự án của bạn nằm ở: ~/code/project1 thì project1 có cấu trúc sau:

├── api #  Laravel
├── client #  React
├── down #  Script dùng để chạy dự án
└── up #  Script dùng để tắt dự án

Bước 4: Setup Laravel container

Các file /folder đáng lưu ý khi setup Laravel container:

├── .gitignore
├── dbdata/ #  Chứa database
├── api.dockerfile #  Setup php-fpm container và các lib cần thiết
├── docker-compose.default.yml
└── docker-compose.yml #  File được ignore, có nội dung gần giống với docker-compose.default.yml

Các file có nội dung sau:

# api.dockerfile
FROM php:7.0.17-fpm

RUN apt-get update && apt-get install -y libmcrypt-dev libpq-dev \
    libmagickwand-dev --no-install-recommends \
    && pecl install imagick \
    && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \
    && docker-php-ext-enable imagick \
    && docker-php-ext-install mcrypt pdo pdo_pgsql pgsql

RUN mkdir /code
# docker-compose.default.yml
version: '2'

networks:
    proxy:
        external:
            name: common_proxy

services:
    project1_db:
        container_name: project1_db
        image: "postgres:10.1"
        environment:
            - POSTGRES_USER=docker
            - POSTGRES_PASSWORD=docker
            - POSTGRES_DB=docker
        ports:
            - "5433:5432"
        volumes:
            - ./dbdata:/var/lib/postgresql/data/:cached
        networks:
            - proxy

    project1_api:
        container_name: project1_api
        build:
            context: ./
            dockerfile: api.dockerfile
        working_dir: /code
        volumes:
            - .:/code:cached
        environment:
            - "DB_CONNECTION=pgsql"
            - "DB_PORT=5432"
            - "DB_HOST=project1_db"
            - "DB_DATABASE=docker"
            - "DB_USERNAME=docker"
            - "DB_PASSWORD=docker"
        networks:
            - proxy

Bước 5: Setup React container

Các file /folder đáng lưu ý khi setup React container:

├── .gitignore
├── client.dockerfile #  Setup node container và các lib cần thiết
├── docker-compose.default.yml
└── docker-compose.yml #  File được ignore, có nội dung gần giống với docker-compose.default.yml

Các file có nội dung sau:

# client.dockerfile
FROM node:carbon

RUN curl -o- -L https://yarnpkg.com/install.sh | bash

RUN mkdir /code

WORKDIR /code

ADD ./package.json /code/

RUN yarn
# docker-compose.default.yml
version: '2'

networks:
    proxy:
        external:
            name: common_proxy

services:
    project1_client:
        container_name: project1_client
        build:
            context: ./
            dockerfile: client.dockerfile
        command: bash -c "yarn start"
        working_dir: /code
        ports:
            - "4004:4004"
        volumes:
            - .:/code:cached
            - /code/node_modules
        networks:
            - proxy

Client dùng port 4004 vì trong bài này, tôi dùng webpack-dev-server chạy port 4004, bạn có thể dùng bất kì port nào khác.

Bước 6: Viết script tắt / mở cả dự án

File updown có nội dung:

# up
docker-compose -f api/docker-compose.yml up -d
docker-compose -f client/docker-compose.yml up -d
docker-compose -f ../../docker/nginx/docker-compose.yml up -d
# down
docker-compose -f ../../docker/nginx/docker-compose.yml down
docker-compose -f api/docker-compose.yml down
docker-compose -f client/docker-compose.yml down

Bước 7: Chạy dự án

Thêm project1.test vào /etc/hosts

Copy các file docker-compose.default.yml thành docker-compose.yml

Chạy: ~/code/project1/up

Nếu cần import file SQL vào db:

docker exec -i project1_db psql -U docker docker < file_path.sql

Hoặc tạo db mới để test:

docker exec -it project1_db createdb -U docker docker_test

Với docker đầu tiên là tên user và docker thứ 2 là tên db được quy định trong project1_db

Tắt: ~/code/project1/down