I. Giới thiệu
Trong những năm qua, Docker trở thành 1 giải pháp thường xuyên được sử dụng để triển khai nhanh các ứng dụng nhờ vào đơn giản hóa việc chạy và triển khai các ứng dụng trong container
(thùng chứa). Khi chúng ta sử dụng LEMP
, ví dụ, với PHP
, Nginx
, Mysql
và Laravel framework
, Docker có thể đơn giản hóa quá trình thiết lập
Docker Compose đã đơn giản hóa hơn nữa quá trình phát triển bằng cách cho phép các nhà phát triển xác định cơ sở hạ tầng của họ, bao gồm các dịch vụ ứng dụng (application services
), mạng (networks
) và khối lượng (volums
) trong một file duy nhất. Docker Compose cung cấp một giải pháp thay thế hiệu quả để chạy nhiều lệnh tạo vùng chứa docker và các lệnh chạy container của docker.
Trong hướng dẫn này, bạn sẽ xây dựng một ứng dụng web Laravel, với Nginx
là máy chủ web và MySQL
là cơ sở dữ liệu, tất cả bên trong các container Docker. Bạn sẽ xác định toàn bộ cấu hình trong tệp soạn thảo docker, cùng với các tệp cấu hình cho PHP, MySQL và Nginx.
II. Xây dựng ứng dụng
1. Tải Laravel và cài đặt các Dependencies
Ở bước này, chúng ta cài đặt Laravel, các bạn có thể vào doc của Laravel để xem lại cách cài tại đây
Đầu tiên, các bạn vào thư mục làm việc của các bạn (ở đây tôi chọn /var/www
) sau đó tiến hành clone code Laravel về với lệnh
1 2 3 | cd <span class="token operator">~</span> git clone https<span class="token punctuation">:</span><span class="token comment">//github.com/laravel/laravel.git laravel-app</span> |
Đi vào thư mục laravel-app vừa được tạo ra
1 2 | docker run <span class="token operator">--</span>rm <span class="token operator">-</span>v $<span class="token punctuation">(</span>pwd<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token operator">/</span>app composer install |
Tiếp theo, sử dụng Docker composer image để mount (gắn kết) các thư mục mà bạn cần trong project và tránh việc phải cài Composer globally
Sử dụng -v và -rm với docker run tạo ra các container, các container này sẽ được kết nối với thư mục của bạn hiện tại trước khi bị xóa. Việc này sẽ copy thư mục laravel-app của bạn vào container và đảm bảo rằng thư mục vendor được tạo bên trong là bản copy của thư mục gốc của bạn
Cuối cùng, ta cấp quyền trên thư mục project với quyền non-root user:
1 2 | sudo chown <span class="token operator">-</span>R <span class="token variable">$USER</span><span class="token punctuation">:</span><span class="token variable">$USER</span> <span class="token operator">~</span><span class="token operator">/</span>laravel<span class="token operator">-</span>app |
2. Tạo Docker Compose File
Xây dựng các ứng dụng của bạn với Docker Compose sẽ giúp bạn đơn giản hóa việc phải thiết lập hạ tầng. Để thiết lập ứng dụng Laravel, chúng ta sẽ một file docker-compose
để xác định web server, database và các service của ứng dụng.
Trong thư mục laravel-app
, chúng ta tạo file docker-composer.yml
với nội dung sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | version<span class="token punctuation">:</span> <span class="token single-quoted-string string">'3'</span> services<span class="token punctuation">:</span> <span class="token shell-comment comment">#PHP Service</span> app<span class="token punctuation">:</span> build<span class="token punctuation">:</span> context<span class="token punctuation">:</span> <span class="token punctuation">.</span> dockerfile<span class="token punctuation">:</span> Dockerfile image<span class="token punctuation">:</span> digitalocean<span class="token punctuation">.</span>com<span class="token operator">/</span>php container_name<span class="token punctuation">:</span> app restart<span class="token punctuation">:</span> unless<span class="token operator">-</span>stopped tty<span class="token punctuation">:</span> <span class="token boolean">true</span> environment<span class="token punctuation">:</span> <span class="token constant">SERVICE_NAME</span><span class="token punctuation">:</span> app <span class="token constant">SERVICE_TAGS</span><span class="token punctuation">:</span> dev working_dir<span class="token punctuation">:</span> <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>www volumes<span class="token punctuation">:</span> <span class="token operator">-</span> <span class="token punctuation">.</span><span class="token operator">/</span><span class="token punctuation">:</span><span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>www <span class="token operator">-</span> <span class="token punctuation">.</span><span class="token operator">/</span>php<span class="token operator">/</span>local<span class="token punctuation">.</span>ini<span class="token punctuation">:</span><span class="token operator">/</span>usr<span class="token operator">/</span>local<span class="token operator">/</span>etc<span class="token operator">/</span>php<span class="token operator">/</span>conf<span class="token punctuation">.</span>d<span class="token operator">/</span>local<span class="token punctuation">.</span>ini networks<span class="token punctuation">:</span> <span class="token operator">-</span> app<span class="token operator">-</span>network <span class="token shell-comment comment">#Nginx Service</span> webserver<span class="token punctuation">:</span> image<span class="token punctuation">:</span> nginx<span class="token punctuation">:</span>alpine container_name<span class="token punctuation">:</span> webserver restart<span class="token punctuation">:</span> unless<span class="token operator">-</span>stopped tty<span class="token punctuation">:</span> <span class="token boolean">true</span> ports<span class="token punctuation">:</span> <span class="token operator">-</span> <span class="token double-quoted-string string">"80:80"</span> <span class="token operator">-</span> <span class="token double-quoted-string string">"443:443"</span> volumes<span class="token punctuation">:</span> <span class="token operator">-</span> <span class="token punctuation">.</span><span class="token operator">/</span><span class="token punctuation">:</span><span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>www <span class="token operator">-</span> <span class="token punctuation">.</span><span class="token operator">/</span>nginx<span class="token operator">/</span>conf<span class="token punctuation">.</span>d<span class="token operator">/</span><span class="token punctuation">:</span><span class="token operator">/</span>etc<span class="token operator">/</span>nginx<span class="token operator">/</span>conf<span class="token punctuation">.</span>d<span class="token operator">/</span> networks<span class="token punctuation">:</span> <span class="token operator">-</span> app<span class="token operator">-</span>network <span class="token shell-comment comment">#MySQL Service</span> db<span class="token punctuation">:</span> image<span class="token punctuation">:</span> mysql<span class="token punctuation">:</span><span class="token number">5.7</span><span class="token number">.22</span> container_name<span class="token punctuation">:</span> db restart<span class="token punctuation">:</span> unless<span class="token operator">-</span>stopped tty<span class="token punctuation">:</span> <span class="token boolean">true</span> ports<span class="token punctuation">:</span> <span class="token operator">-</span> <span class="token double-quoted-string string">"3306:3306"</span> environment<span class="token punctuation">:</span> <span class="token constant">MYSQL_DATABASE</span><span class="token punctuation">:</span> laravel <span class="token constant">MYSQL_ROOT_PASSWORD</span><span class="token punctuation">:</span> your_mysql_root_password <span class="token constant">SERVICE_TAGS</span><span class="token punctuation">:</span> dev <span class="token constant">SERVICE_NAME</span><span class="token punctuation">:</span> mysql volumes<span class="token punctuation">:</span> <span class="token operator">-</span> dbdata<span class="token punctuation">:</span><span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>lib<span class="token operator">/</span>mysql<span class="token operator">/</span> <span class="token operator">-</span> <span class="token punctuation">.</span><span class="token operator">/</span>mysql<span class="token operator">/</span>my<span class="token punctuation">.</span>cnf<span class="token punctuation">:</span><span class="token operator">/</span>etc<span class="token operator">/</span>mysql<span class="token operator">/</span>my<span class="token punctuation">.</span>cnf networks<span class="token punctuation">:</span> <span class="token operator">-</span> app<span class="token operator">-</span>network <span class="token shell-comment comment">#Docker Networks</span> networks<span class="token punctuation">:</span> app<span class="token operator">-</span>network<span class="token punctuation">:</span> driver<span class="token punctuation">:</span> bridge <span class="token shell-comment comment">#Volumes</span> volumes<span class="token punctuation">:</span> dbdata<span class="token punctuation">:</span> driver<span class="token punctuation">:</span> local |
Giải thích file docker-compose.yml
1 chút, ở đây chúng ta xác định có 3 services là : app, webserver và database
- app: Định nghĩa này chứa ứng dụng Laravel và chạy 1 custom Docker image: digitalocean.com/php, cái này được chỉ ra tại Bước 4, nó thiết lập working_dir trong container tới thư mục /var/www
- webserver: Định nghĩa của dịch vụ này sẽ pull image nginx:alpine từ docker và chạy trên cổng 80 và 443
- db: Định nghĩa của dịch vụ được pull về từ image mysql:5.7.22 từ Docker và đã xác định được một vài biến môi trường bao gồm cả
database
được thiết lập cho ứng dụng của bạn được đặt tên là laravel ( đây là name database), đồng thời kèm theo passwordroot
cho database, bạn có thể đặt tên khác cho database và đặt password mà bạn muốn. Service này sẽ map port 3306 trên host tới port 3306 trên container. - container_name: Để xác định tên của container, tương ứng với tên của dịch vụ, nếu bạn không chỉ định tên, Docker sẽ gán tên cho mỗi container đó.
- app-network: để tạo kết nối giữa các container, các dịch vụ sẽ được kết nối qua app-network.
- dbdata: là 1 volume tồn tại nội dung của folder /var/lib/mysql bên trong container. Điều này cho phép bạn dừng và khởi động service db mà không bị mất dữ liệu, ở cuối file
docker-compose.yml
ta cũng thấy có dbdata, với định nghĩa này, ta có thể sử dụng volumn này qua nhiều services.
3. Tạo Dockerfile
Các bạn tạo 1 file là Dockerfile
và được lưu tại ~/laravel-app, nội dung của file như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | <span class="token constant">FROM</span> php<span class="token punctuation">:</span><span class="token number">7.2</span><span class="token operator">-</span>fpm <span class="token shell-comment comment"># Copy composer.lock and composer.json</span> <span class="token constant">COPY</span> composer<span class="token punctuation">.</span>lock composer<span class="token punctuation">.</span>json <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>www<span class="token operator">/</span> <span class="token shell-comment comment"># Set working directory</span> <span class="token constant">WORKDIR</span> <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>www <span class="token shell-comment comment"># Install dependencies</span> <span class="token constant">RUN</span> apt<span class="token operator">-</span>get update <span class="token operator">&&</span> apt<span class="token operator">-</span>get install <span class="token operator">-</span>y build<span class="token operator">-</span>essential <span class="token keyword">default</span><span class="token operator">-</span>mysql<span class="token operator">-</span>client libpng<span class="token operator">-</span>dev libjpeg62<span class="token operator">-</span>turbo<span class="token operator">-</span>dev libfreetype6<span class="token operator">-</span>dev locales zip jpegoptim optipng pngquant gifsicle vim unzip git curl <span class="token shell-comment comment"># Clear cache</span> <span class="token constant">RUN</span> apt<span class="token operator">-</span>get clean <span class="token operator">&&</span> rm <span class="token operator">-</span>rf <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>lib<span class="token operator">/</span>apt<span class="token operator">/</span>lists<span class="token operator">/</span><span class="token operator">*</span> <span class="token shell-comment comment"># Install extensions</span> <span class="token constant">RUN</span> docker<span class="token operator">-</span>php<span class="token operator">-</span>ext<span class="token operator">-</span>install pdo_mysql mbstring zip exif pcntl <span class="token constant">RUN</span> docker<span class="token operator">-</span>php<span class="token operator">-</span>ext<span class="token operator">-</span>configure gd <span class="token operator">--</span>with<span class="token operator">-</span>gd <span class="token operator">--</span>with<span class="token operator">-</span>freetype<span class="token operator">-</span>dir<span class="token operator">=</span><span class="token operator">/</span>usr<span class="token operator">/</span><span class="token keyword">include</span><span class="token operator">/</span> <span class="token operator">--</span>with<span class="token operator">-</span>jpeg<span class="token operator">-</span>dir<span class="token operator">=</span><span class="token operator">/</span>usr<span class="token operator">/</span><span class="token keyword">include</span><span class="token operator">/</span> <span class="token operator">--</span>with<span class="token operator">-</span>png<span class="token operator">-</span>dir<span class="token operator">=</span><span class="token operator">/</span>usr<span class="token operator">/</span><span class="token keyword">include</span><span class="token operator">/</span> <span class="token constant">RUN</span> docker<span class="token operator">-</span>php<span class="token operator">-</span>ext<span class="token operator">-</span>install gd <span class="token shell-comment comment"># Install composer</span> <span class="token constant">RUN</span> curl <span class="token operator">-</span>sS https<span class="token punctuation">:</span><span class="token comment">//getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer</span> <span class="token shell-comment comment"># Add user for laravel application</span> <span class="token constant">RUN</span> groupadd <span class="token operator">-</span>g <span class="token number">1000</span> www <span class="token constant">RUN</span> useradd <span class="token operator">-</span>u <span class="token number">1000</span> <span class="token operator">-</span>ms <span class="token operator">/</span>bin<span class="token operator">/</span>bash <span class="token operator">-</span>g www www <span class="token shell-comment comment"># Copy existing application directory contents</span> <span class="token constant">COPY</span> <span class="token punctuation">.</span> <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>www <span class="token shell-comment comment"># Copy existing application directory permissions</span> <span class="token constant">COPY</span> <span class="token operator">--</span>chown<span class="token operator">=</span>www<span class="token punctuation">:</span>www <span class="token punctuation">.</span> <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>www <span class="token shell-comment comment"># Change current user to www</span> <span class="token constant">USER</span> www <span class="token shell-comment comment"># Expose port 9000 and start php-fpm server</span> <span class="token constant">EXPOSE</span> <span class="token number">9000</span> <span class="token constant">CMD</span> <span class="token punctuation">[</span><span class="token double-quoted-string string">"php-fpm"</span><span class="token punctuation">]</span> |
Giải thích file Dockerfile 1 chút:
Đầu tiên Dockerfile tạo 1 image trên cùng của một Docker image là php:7.2-fpm, image này đã được cài sẵn php-fpm
RUN để thực hiện việc update, install và cấu hình bên trong 1 container, bao gồm cả việc chỉ ra
user
và groupwww
WORKDIR chỉ ra thư mục làm việc dành cho ứng dụng trên container.
Tạo một người dùng và nhóm chuyên dụng với các quyền hạn chế sẽ giảm thiểu lỗ hổng vốn có khi chạy các container Docker, vì mặc định là root. Thay vì chạy container này với quyền root, chúng ta đã tạo user www, người có quyền truy cập đọc / ghi vào thư mục
/var/www
thông qua lệnh COPY và kèm theo –chown để sao chép quyền của thư mục ứng dụng .Cuối cùng, lệnh EXPOSE hiển thị một cổng trong container, 9000, cho máy chủ
php-fpm
. CMD chỉ định lệnh sẽ chạy khi container được tạo. Ở đây, CMD chỉ định “php-fpm”, sẽ khởi động máy chủ.
4. Cấu hình PHP
Bây giờ bạn xác định cơ sở hạ tầng thông qua docker-compose.yml, bạn có thể cấu hình dịch vụ PHP để hoạt động xử lý các yêu cầu đến từ Nginx
Để cấu hình PHP, bạn sẽ tạo tệp local.ini bên trong thư mục ~/laravel-app/php. Đây là tệp mà bạn đã gắn kết với /usr/local/etc/php/conf.d/local.ini bên trong vùng chứa trong phần 2. Tạo tệp này sẽ cho phép bạn ghi đè tệpphp.ini
mặc định mà PHP đọc khi nó bắt đầu khởi động:
5. Cấu hình Nginx
Để cấu hình cho Nginx, bạn tạo 1 file app.conf bên trong thư mục ~/laravel-app/nginx/conf.d/, với nội dung:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | server <span class="token punctuation">{</span> listen <span class="token number">80</span><span class="token punctuation">;</span> index index<span class="token punctuation">.</span>php index<span class="token punctuation">.</span>html<span class="token punctuation">;</span> error_log <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>log<span class="token operator">/</span>nginx<span class="token operator">/</span>error<span class="token punctuation">.</span>log<span class="token punctuation">;</span> access_log <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>log<span class="token operator">/</span>nginx<span class="token operator">/</span>access<span class="token punctuation">.</span>log<span class="token punctuation">;</span> root <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>www<span class="token operator">/</span><span class="token keyword">public</span><span class="token punctuation">;</span> location <span class="token operator">~</span> <span class="token punctuation">.</span>php$ <span class="token punctuation">{</span> try_files <span class="token variable">$uri</span> <span class="token operator">=</span><span class="token number">404</span><span class="token punctuation">;</span> fastcgi_split_path_info <span class="token operator">^</span><span class="token punctuation">(</span><span class="token punctuation">.</span><span class="token operator">+</span><span class="token punctuation">.</span>php<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token operator">/</span><span class="token punctuation">.</span><span class="token operator">+</span><span class="token punctuation">)</span>$<span class="token punctuation">;</span> fastcgi_pass app<span class="token punctuation">:</span><span class="token number">9000</span><span class="token punctuation">;</span> fastcgi_index index<span class="token punctuation">.</span>php<span class="token punctuation">;</span> <span class="token keyword">include</span> fastcgi_params<span class="token punctuation">;</span> fastcgi_param <span class="token constant">SCRIPT_FILENAME</span> <span class="token variable">$document_root</span><span class="token variable">$fastcgi_script_name</span><span class="token punctuation">;</span> fastcgi_param <span class="token constant">PATH_INFO</span> <span class="token variable">$fastcgi_path_info</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> location <span class="token operator">/</span> <span class="token punctuation">{</span> try_files <span class="token variable">$uri</span> <span class="token variable">$uri</span><span class="token operator">/</span> <span class="token operator">/</span>index<span class="token punctuation">.</span>php<span class="token operator">?</span><span class="token variable">$query_string</span><span class="token punctuation">;</span> gzip_static on<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
6. Cấu hình MySQL
Tạo file my.cnf bên trong thư mục ~/laravel-app/mysql với nội dung
1 2 3 4 | <span class="token punctuation">[</span>mysqld<span class="token punctuation">]</span> general_log <span class="token operator">=</span> <span class="token number">1</span> general_log_file <span class="token operator">=</span> <span class="token operator">/</span><span class="token keyword">var</span><span class="token operator">/</span>lib<span class="token operator">/</span>mysql<span class="token operator">/</span>general<span class="token punctuation">.</span>log |
7. Chạy các container và sửa cấu hình môi trường
Bây giờ bạn đã xác định tất cả các dịch vụ của mình trong docker-compose và tạo các tệp cấu hình cho các dịch vụ này, bạn có thể khởi động các container. Tuy nhiên, là bước cuối cùng, chúng ta sẽ tạo file .env từ .env.example để xác định môi trường trong ứng dụng Laravel.
1 2 | cp <span class="token punctuation">.</span>env<span class="token punctuation">.</span>example <span class="token punctuation">.</span>env |
Chúng ta sẽ cấu hình cụ thể khi chúng ta bắt đầu chạy container.
Với tất cả các dịch vụ được xác định trong tệp docker-compose, bạn chỉ cần đưa ra một lệnh duy nhất để bắt đầu tất cả các vùng chứa, tạo khối lượng và thiết lập và kết nối các mạng:
1 2 | docker<span class="token operator">-</span>compose up <span class="token operator">-</span>d |
Khi bạn chạy docker-compose lần đầu tiên, nó sẽ tải xuống tất cả các image Docker cần thiết. Khi image được tải xuống và lưu trữ trong máy cục bộ của bạn, Compose sẽ tạo các container. Cờ -d
để nói rằng, việc chạy các container là chạy ngầm.
Bạn có thể sửa đổi cấu hình .env trong container bằng lệnh docker-compose exec
ví dụ để mở file .env ta dùng:
1 2 | docker<span class="token operator">-</span>compose exec app nano <span class="token punctuation">.</span>env |
ngoài ra các bạn chạy thêm
1 2 3 4 | docker<span class="token operator">-</span>compose exec app php artisan key<span class="token punctuation">:</span>generate docker<span class="token operator">-</span>compose exec app php artisan config<span class="token punctuation">:</span>cache |
Kết quả các bạn sẽ thấy như sau:
Chúc các bạn thành công !