CyStack logo
  • Sản phẩm & Dịch vụ
  • Giải pháp
  • Bảng giá
  • Công ty
  • Tài liệu
Vi

vi

Trang chủHướng dẫnCách cài đặt wordpress trên Docker Compose

Cách cài đặt wordpress trên Docker Compose

CyStack blog 21 phút để đọc
CyStack blog16/10/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 21 minutes

Trong thế giới kỹ thuật số ngày nay, WordPress vẫn là một “ông lớn” trong lĩnh vực hệ thống quản lý nội dung (CMS), được hàng triệu website tin dùng từ các blog cá nhân đến những trang thương mại điện tử phức tạp. Tuy nhiên, việc cài đặt và quản lý WordPress theo cách truyền thống thường tiêu tốn nhiều thời gian và công sức, đặc biệt khi bạn phải tự tay thiết lập toàn bộ môi trường như một LAMP (Linux, Apache, MySQL, PHP) hay LEMP (Linux, Nginx, MySQL, PHP) stack.

cài đặt wordpress trên Docker Compose

May mắn thay với sự ra đời của Docker và Docker Compose, chúng ta có một giải pháp mạnh mẽ để đơn giản hóa quá trình này. Docker cho phép chúng ta đóng gói ứng dụng và tất cả các phụ thuộc vào các “image” tiêu chuẩn, sau đó chạy chúng trong các “container” biệt lập. Docker Compose còn đi xa hơn, giúp chúng ta điều phối nhiều container cùng lúc, ví dụ như ứng dụng WordPress và cơ sở dữ liệu của nó, để chúng có thể giao tiếp một cách liền mạch.

Tôi sẽ chia sẻ với bạn cách chúng ta có thể xây dựng một hệ thống WordPress đa container, sử dụng Docker Compose. Thiết lập này bao gồm một cơ sở dữ liệu MySQL, một máy chủ web Nginx, và chính ứng dụng WordPress. Không chỉ vậy, chúng ta còn đảm bảo an toàn cho website bằng cách tích hợp chứng chỉ TLS/SSL miễn phí từ Let’s Encrypt và thiết lập một cronjob để tự động gia hạn chứng chỉ, giúp trang web của bạn luôn được bảo vệ.

Chuẩn bị

Nếu bạn đang sử dụng Ubuntu phiên bản 16.04 trở xuống, chúng tôi khuyên bạn nên nâng cấp lên phiên bản mới nhất vì Ubuntu không còn hỗ trợ các phiên bản này nữa.

  • Một máy chủ chạy Ubuntu, cùng với một người dùng không phải root có quyền sudo và tường lửa đang hoạt động
  • Cài đặt docker
  • Cài đặt Docker Compose
  • Đăng ký 1 domain
  • Cấu hình 2 bản ghị DNS sau
    • Bản ghi A cho your_domain trỏ đến địa chỉ IP công khai của máy chủ của bạn.
    • Bản ghi A cho www.your_domain trỏ đến địa chỉ IP công khai của máy chủ của bạn.

Các bước cài đặt

Bước 1: Định nghĩa cấu hình cho Web Server Nginx

Bước 2: Định nghĩa các biến môi trường

Bước 3: Định nghĩa các dịch vụ với Docker Compose

Bước 4: Lấy chứng chỉ SSL và thông tin xác thực

Bước 5: Sửa đổi cấu hình Web Server và định nghĩa dịch vụ

Bước 6: Hoàn tất cài đặt thông qua giao diện Web

Bước 7: Tự động gia hạn chứng chỉ SSL

Bước 1: Định nghĩa cấu hình cho Web Server Nginx

Trước khi chúng ta khởi động bất kỳ container nào, việc đầu tiên là định nghĩa cấu hình cho máy chủ web Nginx của chúng ta. File cấu hình này sẽ chứa các block location dành riêng cho WordPress và một block location đặc biệt để chuyển hướng các yêu cầu xác minh của Let’s Encrypt đến client Certbot để gia hạn chứng chỉ tự động.

Đầu tiên, chúng ta tạo một thư mục dự án cho thiết lập WordPress. Tôi gọi nó là wordpress, bạn có thể đặt tên khác tùy ý:

mkdir wordpress

Sau đó, chúng ta di chuyển vào thư mục này:

cd wordpress

Tiếp theo, tạo một thư mục để chứa file cấu hình Nginx:

mkdir nginx-conf

Mở file cấu hình Nginx bằng nano hoặc trình soạn thảo yêu thích của bạn:

nano nginx-conf/nginx.conf

Trong file này, bạn sẽ thêm một server block với các chỉ thị cho server_namedocument root, cùng với các location block để xử lý yêu cầu của Certbot, PHP, và các tài nguyên tĩnh. Hãy nhớ thay thế your_domain bằng tên miền của bạn:

server {
        listen 80;
        listen [::]:80;

        server_name your_domain www.your_domain;

        index index.php index.html index.htm;

        root /var/www/html;

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }

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

        location ~ \\\\.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\\\\.php)(/.+)$;
                fastcgi_pass wordpress: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 ~ /\\\\.ht {
                deny all;
        }

        location = /favicon.ico {
                log_not_found off; access_log off;
        }
        location = /robots.txt {
                log_not_found off; access_log off; allow all;
        }
        location ~* \\\\.(css|gif|ico|jpeg|jpg|js|png)$ {
                expires max;
                log_not_found off;
        }
}

Khối server block của chúng ta bao gồm các thông tin sau:

Các directive:

  • listen: Chỉ định cho Nginx lắng nghe trên cổng 80, cho phép bạn sử dụng plugin webroot của Certbot để yêu cầu chứng chỉ. Lưu ý rằng ở đây bạn chưa cấu hình cổng 443 bạn sẽ cập nhật cấu hình để bổ sung SSL sau khi đã lấy chứng chỉ thành công.
  • server_name: Xác định tên máy chủ và server block sẽ được dùng cho các request đến server của bạn. Hãy chắc chắn thay your_domain bằng domain thật của bạn.
  • index: Xác định các file sẽ được dùng làm index khi xử lý request. Ở đây, bạn đã chỉnh lại thứ tự mặc định, đưa index.php lên trước index.html, để Nginx ưu tiên xử lý file index.php khi có thể.
  • root: Xác định thư mục gốc cho các request đến server. Thư mục này, /var/www/html, được tạo ra tại thời điểm build như một mount point theo hướng dẫn trong Dockerfile của WordPress. Các hướng dẫn này cũng đảm bảo rằng các file từ bản phát hành WordPress sẽ được mount vào volume này.

Các Location Block:

  • location ~ /.well-known/acme-challenge: Xử lý các request đến thư mục .well-known, nơi Certbot sẽ đặt file tạm để xác thực rằng DNS domain của bạn trỏ về server. Với cấu hình này, bạn có thể dùng plugin webroot của Certbot để lấy chứng chỉ cho domain.
  • location /: Sử dụng directive try_files để kiểm tra file khớp với từng URI request. Thay vì trả về 404 Not Found theo mặc định, request sẽ được chuyển tiếp đến file index.php của WordPress cùng với tham số request.
  • location ~ .php$: Xử lý các request PHP và proxy đến container wordpress. Vì WordPress Docker image được xây dựng dựa trên php:fpm, bạn sẽ thêm các cấu hình dành riêng cho giao thức FastCGI. Nginx yêu cầu một PHP processor độc lập để xử lý PHP request, và ở đây sẽ là php-fpm tích hợp trong image php:fpm. Block này còn bao gồm các directive, biến và tùy chọn dành riêng cho FastCGI để proxy request đến WordPress trong container, thiết lập index ưu tiên cho URI request, và phân tích các URI request.
  • location ~ /.ht: Xử lý các file .htaccess vì Nginx sẽ không phục vụ chúng. Directive deny_all đảm bảo các file .htaccess sẽ không bao giờ được gửi cho người dùng.
  • location = /favicon.ico, location = /robots.txt: Đảm bảo các request đến /favicon.ico/robots.txt sẽ không bị ghi log.
  • location ~ .(css|gif|ico|jpeg|jpg|js|png)$**: Tắt log cho các request asset tĩnh và đảm bảo rằng chúng có thể được cache lâu dài, vì những asset này thường tốn chi phí khi phục vụ.

Sau khi chỉnh sửa xong, hãy lưu và thoát file. Nếu dùng nano, nhấn CTRL+X, sau đó Y, rồi ENTER.

Khi cấu hình Nginx đã hoàn tất, bạn có thể chuyển sang bước tạo biến môi trường (environment variables) để truyền cho các container ứng dụng và database khi runtime.

Bước 2: Định nghĩa các biến môi trường

Các container cơ sở dữ liệu và ứng dụng WordPress của chúng ta cần truy cập vào một số biến môi trường để dữ liệu ứng dụng được lưu trữ và truy cập đúng cách. Các biến này bao gồm thông tin nhạy cảm (mật khẩu root MySQL, tên và mật khẩu người dùng cơ sở dữ liệu ứng dụng) và thông tin không nhạy cảm (tên và host cơ sở dữ liệu ứng dụng).

Thay vì đặt tất cả các giá trị này trực tiếp trong file docker-compose.yml, chúng ta sẽ đặt các giá trị nhạy cảm vào một file .env và hạn chế việc chia sẻ nó. Điều này giúp ngăn chặn các giá trị này bị sao chép vào các kho lưu trữ dự án của bạn và bị lộ công khai.

Trong thư mục dự án chính của bạn, ~/wordpress, hãy mở một file có tên .env:

nano .env

Bạn thêm các tên biến và giá trị sau vào file. Hãy nhớ cung cấp các giá trị của riêng bạn cho mỗi biến:

MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_USER=your_wordpress_database_user
MYSQL_PASSWORD=your_wordpress_database_password

Lưu và đóng file khi bạn hoàn tất chỉnh sửa.

Vì file .env chứa thông tin nhạy cảm, chúng ta cần đảm bảo rằng nó được đưa vào file .gitignore.dockerignore của dự án. Điều này sẽ báo cho Git và Docker biết những file nào không được sao chép vào kho lưu trữ Git và Docker image tương ứng.

Nếu bạn có kế hoạch làm việc với Git, hãy khởi tạo thư mục làm việc hiện tại của bạn thành một repository:

git init

Sau đó tạo và mở file .gitignore:

nano .gitignore

Thêm .env vào file:

.env

Lưu và đóng file. Tương tự, thêm .env vào file .dockerignore:

nano .dockerignore

Thêm .env vào file:

.env

Bạn cũng có thể tùy chọn thêm các file và thư mục liên quan đến quá trình phát triển ứng dụng của bạn dưới đây:

.env
.git
docker-compose.yml
.dockerignore

Lưu và đóng file khi bạn hoàn tất.

Với các thông tin nhạy cảm đã được bảo mật, giờ là lúc định nghĩa các dịch vụ chính trong file docker-compose.yml của chúng ta.

Bước 3: Định nghĩa các dịch vụ với Docker Compose

File docker-compose.yml của chúng ta sẽ chứa các định nghĩa dịch vụ cho toàn bộ thiết lập. Một “service” trong Compose là một container đang chạy, và định nghĩa dịch vụ sẽ chỉ rõ cách mỗi container sẽ hoạt động.

Chúng ta sẽ tạo các container khác nhau cho cơ sở dữ liệu, ứng dụng WordPress, và máy chủ web Nginx. Ngoài ra, chúng ta còn tạo một container để chạy client Certbot nhằm lấy chứng chỉ SSL cho máy chủ web.

Để bắt đầu, hãy tạo và mở file docker-compose.yml:

nano docker-compose.yml

Thêm đoạn mã sau để định nghĩa phiên bản Compose file và dịch vụ cơ sở dữ liệu db:

version: '3'

services:
  db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'
    networks:
      - app-network

Giải thích dịch vụ db:

  • image: Chỉ định cho Compose biết cần pull image nào để tạo container. Ở đây bạn đang pin image mysql:8.0 để tránh xung đột trong tương lai, do image mysql:latest sẽ liên tục được cập nhật.
  • container_name: Chỉ định tên cho container.
  • restart: Định nghĩa chính sách khởi động lại của container. Giá trị mặc định là no, nhưng ở đây bạn đã cấu hình để container tự động khởi động lại trừ khi được dừng thủ công.
  • env_file: Tùy chọn này cho Compose biết rằng bạn muốn thêm các biến môi trường từ một file tên .env, nằm trong build context. Trong trường hợp này, build context chính là thư mục hiện tại của bạn.
  • environment: Tùy chọn này cho phép bạn thêm các biến môi trường bổ sung, ngoài những biến đã được định nghĩa trong file .env. Ở đây bạn sẽ thiết lập biến MYSQL_DATABASE=wordpress để đặt tên cho cơ sở dữ liệu ứng dụng. Vì đây không phải thông tin nhạy cảm, bạn có thể khai báo trực tiếp trong file docker-compose.yml.
  • volumes: Tại đây, bạn gắn (mount) một volume có tên dbdata vào thư mục /var/lib/mysql trên container. Đây là thư mục dữ liệu tiêu chuẩn của MySQL trong hầu hết các bản phân phối.
  • command: Tùy chọn này cho phép bạn ghi đè lệnh CMD mặc định của image. Trong trường hợp này, bạn thêm một tùy chọn vào lệnh mysqld tiêu chuẩn của Docker image (lệnh khởi động MySQL server trong container). Tùy chọn --default-authentication-plugin=mysql_native_password sẽ thiết lập biến hệ thống --default-authentication-plugin thành mysql_native_password, chỉ định cơ chế xác thực sẽ được sử dụng cho các yêu cầu xác thực mới. Do PHP (và theo đó là image WordPress) không hỗ trợ cơ chế xác thực mặc định mới hơn của MySQL, bạn cần chỉnh cấu hình này để người dùng cơ sở dữ liệu ứng dụng có thể xác thực.
  • networks: Chỉ định rằng dịch vụ ứng dụng của bạn sẽ tham gia vào mạng app-network, được định nghĩa ở cuối file.

Tiếp theo, bên dưới định nghĩa dịch vụ db, thêm định nghĩa cho dịch vụ ứng dụng wordpress của bạn:

...
  wordpress:
    depends_on:
      - db
    image: wordpress:5.1.1-fpm-alpine
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
    networks:
      - app-network

Trong định nghĩa service này, bạn đang đặt tên cho containerxác định chính sách khởi động lại, tương tự như với service db. Ngoài ra, bạn cũng thêm một số tùy chọn riêng cho container này:

  • depends_on: Tùy chọn này đảm bảo các container sẽ khởi động theo đúng thứ tự phụ thuộc, với container wordpress chỉ khởi động sau khi container db đã chạy. Ứng dụng WordPress của bạn phụ thuộc vào sự tồn tại của cơ sở dữ liệu và người dùng ứng dụng, do đó việc thể hiện mối quan hệ phụ thuộc này sẽ giúp ứng dụng khởi động đúng cách.
  • image: Trong cấu hình này, bạn sử dụng image WordPress 5.1.1-fpm-alpine. Như đã đề cập ở Bước 1, image này đảm bảo ứng dụng của bạn sẽ có sẵn php-fpm processorNginx yêu cầu để xử lý PHP. Đây cũng là một alpine image (dựa trên Alpine Linux), giúp giữ kích thước image tổng thể nhỏ gọn.
  • env_file: Tương tự, bạn chỉ định rằng các giá trị sẽ được lấy từ file .env, nơi bạn đã định nghĩa người dùng và mật khẩu cơ sở dữ liệu ứng dụng.
  • environment: Ở đây, bạn sử dụng các giá trị trong file .env, nhưng gán chúng cho các biến môi trường mà WordPress image mong đợi:
    • WORDPRESS_DB_USER
    • WORDPRESS_DB_PASSWORD

    Bạn cũng định nghĩa biến WORDPRESS_DB_HOST, trỏ đến MySQL server chạy trong container db, truy cập được qua cổng mặc định của MySQL là 3306. Biến WORDPRESS_DB_NAME sẽ trùng với giá trị bạn đã khai báo trong service MySQL cho biến MYSQL_DATABASE: wordpress.

  • volumes: Bạn mount một volume có tên wordpress vào mountpoint /var/www/html do WordPress image tạo ra. Việc dùng named volume theo cách này cho phép bạn chia sẻ mã nguồn ứng dụng với các container khác.
  • networks: Bạn cũng thêm container wordpress vào mạng app-network.

Tiếp theo, bên dưới định nghĩa dịch vụ wordpress, thêm định nghĩa cho dịch vụ máy chủ web webserver (Nginx):

...
  webserver:
    depends_on:
      - wordpress
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

Ở đây, bạn đặt tên cho container và cấu hình nó phụ thuộc vào container wordpress trong thứ tự khởi động. Bạn cũng sử dụng một alpine image, cụ thể là image Nginx 1.15.12-alpine.

Định nghĩa service này cũng bao gồm các tùy chọn sau:

  • ports: Mở cổng 80 để kích hoạt các tùy chọn cấu hình mà bạn đã định nghĩa trong file nginx.conf ở Bước 1.
  • volumes: Bạn định nghĩa kết hợp giữa named volumesbind mounts:
    • wordpress:/var/www/html: Mount mã nguồn ứng dụng WordPress vào thư mục /var/www/html (thư mục bạn đã thiết lập làm root trong khối cấu hình server của Nginx).
    • ./nginx-conf:/etc/nginx/conf.d: Bind mount thư mục cấu hình Nginx trên host vào thư mục tương ứng trong container, đảm bảo mọi thay đổi bạn thực hiện trên host sẽ được phản ánh ngay trong container.
    • certbot-etc:/etc/letsencrypt: Mount chứng chỉ và khóa của Let’s Encrypt cho domain của bạn vào thư mục thích hợp trong container.
  • networks: Bạn cũng thêm container này vào mạng app-network.

Cuối cùng, bên dưới định nghĩa dịch vụ webserver, thêm định nghĩa dịch vụ certbot. Hãy đảm bảo thay thế địa chỉ email và tên miền bằng thông tin của riêng bạn:

...
  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@your_domain --agree-tos --no-eff-email --staging -d your_domain -d www.your_domain

Định nghĩa này yêu cầu Compose pull image certbot/certbot từ Docker Hub. Nó cũng sử dụng named volumes để chia sẻ tài nguyên với container Nginx, bao gồm chứng chỉ và khóa domain trong certbot-etc cũng như mã nguồn ứng dụng trong wordpress.

Một lần nữa, bạn dùng depends_on để chỉ định rằng container certbot sẽ chỉ khởi động sau khi service webserver đã chạy.

Bạn cũng thêm tùy chọn command, chỉ định một subcommand chạy cùng với lệnh mặc định certbot của container. Subcommand certonly sẽ lấy chứng chỉ với các tùy chọn sau:

  • -webroot: Chỉ định cho Certbot dùng plugin webroot để đặt file vào thư mục webroot phục vụ xác thực. Plugin này dựa trên phương thức xác thực HTTP-01, sử dụng HTTP request để chứng minh rằng Certbot có thể truy cập tài nguyên từ một server phản hồi theo tên miền đã cho.
  • -webroot-path: Xác định đường dẫn tới thư mục webroot.
  • -email: Địa chỉ email bạn muốn dùng cho đăng ký và khôi phục.
  • -agree-tos: Xác nhận rằng bạn đồng ý với ACME’s Subscriber Agreement.
  • -no-eff-email: Chỉ định rằng bạn không muốn chia sẻ email của mình với Electronic Frontier Foundation (EFF). (Bạn có thể bỏ tùy chọn này nếu muốn chia sẻ).
  • -staging: Yêu cầu Certbot sử dụng môi trường staging của Let’s Encrypt để lấy chứng chỉ thử nghiệm. Tùy chọn này giúp bạn kiểm thử cấu hình và tránh vượt quá giới hạn số lần request domain.
  • d: Cho phép bạn chỉ định các domain muốn áp dụng cho request. Trong ví dụ này, bạn khai báo your_domainwww.your_domain. Hãy thay thế chúng bằng domain thực tế của bạn.

Bên dưới định nghĩa dịch vụ certbot, thêm định nghĩa cho volumesnetworks:

...
volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge

Khóa volumes ở cấp cao nhất định nghĩa các volume: certbot-etc, wordpress, và dbdata. Khi Docker tạo volume, nội dung của volume sẽ được lưu trong một thư mục trên filesystem của host tại /var/lib/docker/volumes/, được Docker quản lý. Nội dung của mỗi volume sau đó sẽ được mount từ thư mục này vào bất kỳ container nào sử dụng volume đó. Cách làm này cho phép chia sẻ mã nguồn và dữ liệu giữa các container.

Mạng bridge do người dùng định nghĩa app-network cho phép các container giao tiếp với nhau vì chúng cùng chạy trên một Docker daemon host. Điều này giúp tối ưu hóa lưu lượng và giao tiếp trong ứng dụng, vì nó mở tất cả các cổng giữa các container trong cùng một bridge network mà không cần phải expose bất kỳ cổng nào ra bên ngoài. Nhờ đó, các container db, wordpress, và webserver có thể giao tiếp với nhau, và bạn chỉ cần expose cổng 80 để người dùng truy cập giao diện ứng dụng.

Đây là toàn bộ file docker-compose.yml sau khi hoàn tất:

version: '3'

services:
  db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'
    networks:
      - app-network

  wordpress:
    depends_on:
      - db
    image: wordpress:5.1.1-fpm-alpine
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
    networks:
      - app-network

  webserver:
    depends_on:
      - wordpress
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@your_domain --agree-tos --no-eff-email --staging -d your_domain -d www.your_domain

volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge

Lưu và đóng file khi bạn đã hoàn tất chỉnh sửa.

Với các định nghĩa dịch vụ đã sẵn sàng, chúng ta sẵn sàng khởi động các container và kiểm tra yêu cầu chứng chỉ của mình.

Bước 4: Lấy chứng chỉ SSL và thông tin xác thực

Chúng ta sẽ khởi động các container bằng lệnh docker-compose up. Bằng cách thêm cờ -d, lệnh này sẽ chạy các container db, wordpress, và webserver trong nền, trong khi certbot sẽ chạy một lần và thoát.

docker-compose up -d

Bạn sẽ thấy output xác nhận các dịch vụ đã được tạo:

OutputCreating db ... done
Creating wordpress ... done
Creating webserver ... done
Creating certbot   ... done

Sử dụng docker-compose ps để kiểm tra trạng thái các dịch vụ của bạn:

docker-compose ps

Khi hoàn tất, các dịch vụ db, wordpress, và webserver của bạn sẽ có trạng thái Up, và container certbot sẽ thoát với thông báo trạng thái Exit 0.

Output  Name                 Command               State           Ports
-------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
db          docker-entrypoint.sh --def ...   Up       3306/tcp, 33060/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:80->80/tcp
wordpress   docker-entrypoint.sh php-fpm     Up       9000/tcp

Bất kỳ trạng thái nào khác ngoài Up cho các dịch vụ db, wordpress, hoặc webserver, hoặc trạng thái thoát khác 0 cho container certbot đều có nghĩa là bạn cần kiểm tra log dịch vụ bằng lệnh docker-compose logs service_name.

Bây giờ bạn có thể kiểm tra xem chứng chỉ của bạn đã được gắn vào container webserver chưa bằng docker-compose exec:

docker-compose exec webserver ls -la /etc/letsencrypt/live

Nếu yêu cầu chứng chỉ thành công, bạn sẽ thấy output sau:

Outputtotal 16
drwx------    3 root     root          4096 May 10 15:45 .
drwxr-xr-x    9 root     root          4096 May 10 15:45 ..
-rw-r--r--    1 root     root           740 May 10 15:45 README
drwxr-xr-x    2 root     root          4096 May 10 15:45 your_domain

Khi đã biết yêu cầu chứng chỉ của mình sẽ thành công, bạn có thể chỉnh sửa định nghĩa dịch vụ certbot để loại bỏ cờ --staging.

Mở docker-compose.yml:

nano docker-compose.yml

Hãy tìm đến phần trong file có định nghĩa service certbot, sau đó thay thế cờ --staging trong tùy chọn command bằng cờ --force-renewal. Tùy chọn này sẽ yêu cầu Certbot cấp mới một chứng chỉ với các domain giống như chứng chỉ hiện có.

Dưới đây là phần định nghĩa service certbot sau khi đã cập nhật cờ này:

...
  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@your_domain --agree-tos --no-eff-email --force-renewal -d your_domain -d www.your_domain
...

Bây giờ bạn có thể chạy docker-compose up để tạo lại container certbot. Chúng ta sẽ thêm tùy chọn --no-deps để báo cho Compose rằng nó có thể bỏ qua việc khởi động dịch vụ webserver, vì nó đã chạy rồi:

docker-compose up --force-recreate --no-deps certbot

Output sau cho thấy yêu cầu chứng chỉ của bạn đã thành công:

OutputRecreating certbot ... done
Attaching to certbot
certbot      | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot      | Plugins selected: Authenticator webroot, Installer None
certbot      | Renewing an existing certificate
certbot      | Performing the following challenges:
certbot      | http-01 challenge for your_domain
certbot      | http-01 challenge for www.your_domain
certbot      | Using the webroot path /var/www/html for all unmatched domains.
certbot      | Waiting for verification...
certbot      | Cleaning up challenges
certbot      | IMPORTANT NOTES:
certbot      |  - Congratulations! Your certificate and chain have been saved at:
certbot      |    /etc/letsencrypt/live/your_domain/fullchain.pem
certbot      |    Your key file has been saved at:
certbot      |    /etc/letsencrypt/live/your_domain/privkey.pem
certbot      |    Your cert will expire on 2019-08-08. To obtain a new or tweaked
certbot      |    version of this certificate in the future, simply run certbot
certbot      |    again. To non-interactively renew *all* of your certificates, run
certbot      |    "certbot renew"
certbot      |  - Your account credentials have been saved in your Certbot
certbot      |    configuration directory at /etc/letsencrypt. You should make a
certbot      |    secure backup of this folder now. This configuration directory will
certbot      |    also contain certificates and private keys obtained by Certbot so
certbot      |    making regular backups of this folder is ideal.
certbot      |  - If you like Certbot, please consider supporting our work by:
certbot      |
certbot      |    Donating to ISRG / Let's Encrypt:   <https://letsencrypt.org/donate>
certbot      |    Donating to EFF:                    <https://eff.org/donate-le>
certbot      |
certbot exited with code 0

Với chứng chỉ đã có, chúng ta có thể tiếp tục sửa đổi cấu hình Nginx để bao gồm SSL.

Bước 5: Sửa đổi cấu hình Web Server và định nghĩa dịch vụ

Để bật SSL trong cấu hình Nginx, chúng ta cần thêm một chuyển hướng HTTP sang HTTPS, chỉ định vị trí chứng chỉ và khóa SSL của bạn, và thêm các tham số và header bảo mật.

Vì chúng ta sẽ tạo lại dịch vụ webserver để bao gồm các thay đổi này, bạn có thể dừng nó ngay bây giờ:

docker-compose stop webserver

Trước khi sửa đổi file cấu hình, hãy lấy tham số bảo mật Nginx được khuyến nghị từ Certbot bằng curl:

curl -sSLo nginx-conf/options-ssl-nginx.conf <https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf>

Lệnh này sẽ lưu các tham số này vào một file có tên options-ssl-nginx.conf, nằm trong thư mục nginx-conf.

Tiếp theo, xóa file cấu hình Nginx bạn đã tạo trước đó:

rm nginx-conf/nginx.conf

Tạo và mở một phiên bản khác của file:

nano nginx-conf/nginx.conf

Thêm đoạn mã sau vào file để chuyển hướng HTTP sang HTTPS và thêm thông tin xác thực SSL, giao thức và header bảo mật. Hãy nhớ thay thế your_domain bằng tên miền của bạn:

server {
        listen 80;
        listen [::]:80;

        server_name your_domain www.your_domain;

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }

        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name your_domain www.your_domain;

        index index.php index.html index.htm;

        root /var/www/html;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem;

        include /etc/nginx/conf.d/options-ssl-nginx.conf;

        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "no-referrer-when-downgrade" always;
        add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
        # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        # enable strict transport security only if you understand the implications

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

        location ~ \\\\.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\\\\.php)(/.+)$;
                fastcgi_pass wordpress: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 ~ /\\\\.ht {
                deny all;
        }

        location = /favicon.ico {
                log_not_found off; access_log off;
        }
        location = /robots.txt {
                log_not_found off; access_log off; allow all;
        }
        location ~* \\\\.(css|gif|ico|jpeg|jpg|js|png)$ {
                expires max;
                log_not_found off;
        }
}

Khối HTTP server chỉ định webroot cho các yêu cầu gia hạn chứng chỉ của Certbot tới thư mục .well-known/acme-challenge. Nó cũng bao gồm một rewrite directive để chuyển hướng các yêu cầu HTTP từ thư mục gốc sang HTTPS.

Khối HTTPS server kích hoạt SSLHTTP/2. Khối này cũng bao gồm đường dẫn đến chứng chỉ SSL và khóa bí mật (SSL key), cùng với các tham số bảo mật được khuyến nghị bởi Certbot, mà bạn đã lưu trong file nginx-conf/options-ssl-nginx.conf.

Ngoài ra, nó còn khai báo một số HTTP security headers giúp bạn đạt mức đánh giá A trên các công cụ kiểm thử như SSL LabsSecurity Headers. Các header này bao gồm:

  • X-Frame-Options
  • X-Content-Type-Options
  • Referrer-Policy
  • Content-Security-Policy
  • X-XSS-Protection

Header HTTP Strict Transport Security (HSTS) hiện đang bị comment bạn chỉ nên bật nó nếu đã hiểu rõ tác động và kiểm tra chức năng của nó.

Trong khối này cũng có các directive rootindex, cùng với những location block dành riêng cho WordPress đã được đề cập ở Bước 1.

Sau khi chỉnh sửa xong, hãy lưu và đóng file.

Trước khi tạo lại service webserver, bạn cần thêm ánh xạ cổng 443 vào định nghĩa service này.

Mở file docker-compose.yml của bạn:

nano docker-compose.yml

Trong định nghĩa dịch vụ webserver, thêm ánh xạ cổng sau:

...
  webserver:
    depends_on:
      - wordpress
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443" # Thêm dòng này
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

Đây là file docker-compose.yml hoàn chỉnh sau các chỉnh sửa:

version: '3'

services:
  db:
    image: mysql:8.0
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
    command: '--default-authentication-plugin=mysql_native_password'
    networks:
      - app-network

  wordpress:
    depends_on:
      - db
    image: wordpress:5.1.1-fpm-alpine
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
    networks:
      - app-network

  webserver:
    depends_on:
      - wordpress
    image: nginx:1.15.12-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

  certbot:
    depends_on:
      - webserver
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - wordpress:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@your_domain --agree-tos --no-eff-email --force-renewal -d your_domain -d www.your_domain

volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge

Lưu và đóng file khi bạn đã hoàn tất chỉnh sửa.

Tạo lại dịch vụ webserver:

docker-compose up -d --force-recreate --no-deps webserver

Kiểm tra các dịch vụ của bạn bằng docker-compose ps:

docker-compose ps

Output sẽ chỉ ra rằng các dịch vụ db, wordpress, và webserver của bạn đang chạy:

Output  Name                 Command               State                     Ports
----------------------------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
db          docker-entrypoint.sh --def ...   Up       3306/tcp, 33060/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
wordpress   docker-entrypoint.sh php-fpm     Up       9000/tcp

Với các container đang chạy, bạn có thể hoàn tất cài đặt WordPress thông qua giao diện web.

Bước 6: Hoàn tất cài đặt thông qua giao diện Web

Với các container của bạn đang chạy, chúng ta sẽ hoàn tất cài đặt WordPress thông qua giao diện web.

Trong trình duyệt web của bạn, điều hướng đến tên miền của máy chủ của bạn. Hãy nhớ thay thế your_domain bằng tên miền của riêng bạn:

https://your_domain

Bạn sẽ thấy trang chọn ngôn ngữ của WordPress.

Sau khi nhấp vào Continue , bạn sẽ đến trang thiết lập chính, nơi bạn cần chọn tên cho trang web của mình và tên người dùng. Tốt nhất bạn nên chọn một tên người dùng dễ nhớ (thay vì “admin”) và một mật khẩu mạnh. Bạn có thể sử dụng mật khẩu mà WordPress tự động tạo hoặc tạo mật khẩu của riêng mình.

Cuối cùng, bạn cần nhập địa chỉ email của mình và quyết định xem bạn có muốn các công cụ tìm kiếm không lập chỉ mục trang web của bạn hay không:

Nhấp vào Install WordPress (Cài đặt WordPress) ở cuối trang sẽ đưa bạn đến một lời nhắc đăng nhập:

Sau khi đăng nhập, bạn sẽ có quyền truy cập vào bảng điều khiển quản trị WordPress:

Với việc cài đặt WordPress đã hoàn tất, chúng ta có thể thực hiện các bước để đảm bảo rằng các chứng chỉ SSL của bạn sẽ tự động gia hạn.

Bước 7: Tự động gia hạn chứng chỉ SSL

Chứng chỉ Let’s Encrypt có hiệu lực trong 90 ngày. Chúng ta cần thiết lập một quy trình gia hạn tự động để đảm bảo rằng chúng không bị hết hạn. Một cách để làm điều này là tạo một tác vụ với tiện ích lập lịch cron. Trong ví dụ sau, chúng ta sẽ tạo một cronjob để định kỳ chạy một script sẽ gia hạn chứng chỉ của bạn và tải lại cấu hình Nginx.

Đầu tiên, hãy mở một script có tên ssl_renew.sh:

nano ssl_renew.sh

Thêm đoạn mã sau vào script để gia hạn chứng chỉ và tải lại cấu hình máy chủ web của bạn. Hãy nhớ thay thế sammy bằng tên người dùng non-root của bạn:

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/wordpress/
$COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Script này trước tiên gán binary docker-compose vào một biến có tên COMPOSE, đồng thời chỉ định tùy chọn –no-ansi, giúp chạy các lệnh docker-compose mà không có ký tự điều khiển ANSI. Sau đó, nó cũng thực hiện tương tự với binary docker. Cuối cùng, script chuyển đến thư mục dự án ~/wordpress và chạy các lệnh docker-compose sau:

  • docker-compose run: Lệnh này sẽ khởi chạy một container certbot và ghi đè lệnh đã được định nghĩa trong service certbot. Thay vì dùng subcommand certonly, nó sử dụng subcommand renew, nhằm gia hạn các chứng chỉ sắp hết hạn. Ngoài ra còn có tùy chọn -dry-run để kiểm thử script.
  • docker-compose kill: Lệnh này sẽ gửi tín hiệu SIGHUP đến container webserver để tải lại cấu hình Nginx.

Sau đó, script chạy lệnh docker system prune để xóa tất cả container và image không còn được sử dụng.

Đóng file khi bạn đã hoàn tất chỉnh sửa. Cấp quyền thực thi cho nó bằng lệnh sau:

chmod +x ssl_renew.sh

Tiếp theo, mở file crontab của root để chạy script gia hạn theo một khoảng thời gian được chỉ định:

sudo crontab -e

Nếu đây là lần đầu tiên bạn chỉnh sửa file này, bạn sẽ được yêu cầu chọn một trình soạn thảo.

Ở cuối file này, thêm dòng sau:

...
*/5 * * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

Điều này sẽ đặt khoảng thời gian chạy tác vụ là mỗi năm phút, để bạn có thể kiểm tra xem yêu cầu gia hạn của mình đã hoạt động như mong muốn hay chưa. Một file log, cron.log, được tạo để ghi lại output liên quan từ tác vụ.

Sau năm phút, kiểm tra cron.log để xác nhận xem yêu cầu gia hạn đã thành công hay chưa:

tail -f /var/log/cron.log

Output sau xác nhận gia hạn thành công (trong chế độ dry-run):

Output- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/your_domain/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Thoát bằng cách nhấn CTRL+C trong terminal của bạn.

Bạn có thể sửa đổi file crontab để đặt khoảng thời gian hàng ngày. Để chạy script mỗi ngày vào buổi trưa, ví dụ, bạn sẽ sửa đổi dòng cuối cùng của file như sau:

...
0 12 * * * /home/sammy/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1

Bạn cũng sẽ muốn loại bỏ tùy chọn --dry-run khỏi script ssl_renew.sh của mình:

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/wordpress/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Cronjob của bạn sẽ đảm bảo rằng các chứng chỉ Let’s Encrypt của bạn không bị hết hạn bằng cách gia hạn chúng khi đủ điều kiện.

Kết luận

Trong bài blog này, chúng ta đã cùng nhau xây dựng một hệ thống WordPress đầy đủ và bảo mật bằng cách tận dụng sức mạnh của Docker Compose. Bạn đã học cách triển khai WordPress trên một kiến trúc đa container, bao gồm cơ sở dữ liệu MySQL, máy chủ web Nginx hiệu suất cao, và ứng dụng WordPress cốt lõi. Chúng ta cũng đã thành công trong việc tích hợp chứng chỉ TLS/SSL miễn phí từ Let’s Encrypt để đảm bảo an toàn cho website của bạn và thiết lập một cronjob để tự động gia hạn chứng chỉ, giúp website luôn hoạt động mượt mà và bảo mật liên tục.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất