devkit English

Stack, PHP và HTTPS

devkit không kèm database hay web server riêng. Nó dùng các stack Docker Compose bạn đã có và các bản PHP đã cài trên máy. Trang này giải thích cách devkit tìm ra chúng, cách chỉnh các giá trị mặc định, cách debug với Xdebug và cách chạy site qua HTTPS.

Stack

devkit stack add ~/Project/infra/local-stack
devkit stack list
devkit stack up local-stack                # docker compose up -d
devkit stack up local-stack mysql redis    # chỉ các service này
devkit stack restart local-stack redis
devkit stack logs local-stack mysql --tail 100
devkit stack down local-stack              # docker compose down
devkit stack down local-stack mailpit      # docker compose stop mailpit

Stack là một thư mục có file compose. devkit đọc docker compose config (đã mở rộng ${VAR:-default}) và docker compose ps, và không bao giờ sửa thư mục đó. stack down cả stack sẽ xoá container nhưng giữ volume, nên dữ liệu của bạn vẫn còn; khi kèm tên service thì nó chỉ dừng các container đó.

Site dùng stack bạn đặt bằng devkit site set <site> --stack <name>, hoặc stack đầu tiên bạn thêm. site up chạy mọi service đang dừng mà site cần (database và Redis; với caddy thêm Caddy và container PHP) và chờ health check của chúng, nên hiếm khi bạn phải tự chạy stack.

Vai trò của service

Mỗi service nhận một vai trò theo tên image: phần cuối của image, bỏ registry, namespace và tag (bitnami/redis:7 là redis), so khớp chính xác:

ImageVai tròdevkit đọc gì
mysql, mariadb, percona, percona-servermysqlmật khẩu root (MYSQL_ROOT_PASSWORD / MARIADB_ROOT_PASSWORD) và port được publish
redis, valkeyredisport được publish, REDIS_PASSWORD
caddycaddyđịa chỉ admin API
php với tag fpm, hoặc tên có cả php và fpmphpphiên bản PHP lấy từ tag (php:8.3-fpm → 8.3)
minio, localstack, garages3port API và các key
mailpit, mailhogmailport SMTP (container 1025) và hộp thư (container 8025), theo port được publish

Image bạn tự build không cho biết vai trò của nó, nên hãy gắn label. Label luôn được ưu tiên hơn tên image:

services:
  php83:
    build: ./php83
    labels:
      vn.webpress.devkit.role: php            # mysql | redis | caddy | php | s3 | mail | none
      vn.webpress.devkit.php-version: "8.3"
  caddy:
    image: caddy:2
    labels:
      vn.webpress.devkit.caddy-admin: "localhost:2019"   # admin API nằm ở đâu
      vn.webpress.devkit.caddy-server: "srv0"           # server Caddy nào nhận route của devkit
  adminer:
    image: adminer
    labels:
      vn.webpress.devkit.hidden: "true"                  # không đưa vào danh sách của devkit

Trong app, Stacks hiện từng thư mục cùng các service của nó (vai trò, image, port được publish, health, trạng thái), Chạy, Chạy lại, Dừng…, log của container và Hộp thư ↗ của mail. Dừng… cho biết site đang chạy nào có thể dùng database hoặc Redis của stack trước khi dừng bất cứ thứ gì.

devkit 1.0 chưa có lệnh gỡ một stack khỏi danh sách. Thư mục stack không còn tồn tại chỉ hiện một lỗi khi dò tìm.

Mặc định: engine, port, hậu tố tên miền

devkit settings
default engine  artisan
default PHP     (the php on PATH)
domain suffix   localhost
port range      8100-8999

devkit settings set --port-min 9000 --port-max 9499 --dry-run
devkit settings set --port-min 9000 --port-max 9499
FlagMặc địnhThay đổi gì
--default-engine artisan|caddyartisanengine của các site không tự đặt. Site đang chạy theo giá trị này sẽ bị dừng trước; hãy chạy lại chúng với engine mới
--default-php X.Y"" (php trong PATH)PHP của các site không tự đặt; phiên bản đó phải được cài sẵn. Site đang chạy sẽ chuyển ở lần chạy kế tiếp
--port-min N, --port-max N8100, 8999nơi lấy port mới cho site artisan, lane và Vite. Site giữ port nằm ngoài khoảng mới (có cảnh báo)
--domain-suffix slocalhosthost name của caddy kết thúc bằng hậu tố này, từ lần chạy kế tiếp của mỗi site. Mọi hậu tố khác localhost đều cần dòng trong /etc/hosts (xem dưới)

Luôn thử --dry-run trước: nó cho biết thiết lập sẽ thành gì, site đang chạy nào sẽ dừng và nó sẽ cảnh báo gì, mà không thay đổi gì. App làm đúng như vậy: Cài đặt hỏi devkit với --dry-run, hiện câu trả lời trong một sheet (Không site nào đang chạy bị dừng. hoặc danh sách dưới Các site đang chạy sau sẽ dừng), hỏi lại ngay trước khi áp dụng, và nút đổi thành Dừng chúng và áp dụng khi có site sẽ dừng. PHP mặc định được đặt thẳng từ mục PHP.

PHP trên máy

Engine artisan chạy PHP trên máy bạn. devkit tìm mọi phiên bản tại các vị trí quét, là đường dẫn hoặc glob có * và ~:

Mặc định trên macOSMặc định trên Linux
/opt/homebrew/opt/php@*/bin/php
/opt/homebrew/bin/php
/usr/local/opt/php@*/bin/php
~/.config/herd-lite/bin/php
~/.phpenv/versions/*/bin/php (tắt)
/usr/bin/php*
/usr/local/bin/php*
~/.phpenv/versions/*/bin/php
devkit php list                         # mọi binary tìm thấy
devkit php info 8.3                     # php.ini, extension, Xdebug, các site dùng nó
devkit php scan-paths                   # từng vị trí và những gì nó tìm thấy
devkit php scan-paths add '~/tools/php/*/bin/php'
devkit php scan-paths disable '~/.phpenv/versions/*/bin/php'
devkit php scan-paths enable '~/.phpenv/versions/*/bin/php'
devkit php scan-paths rm '~/tools/php/*/bin/php'
devkit php scan-paths reset             # về mặc định của hệ thống này
devkit settings set --default-php 8.3   # site không tự đặt phiên bản sẽ dùng 8.3

Binary được tìm thấy hai lần (symlink và đích của nó) chỉ được liệt kê một lần. Hãy đặt glob trong dấu nháy để shell không tự mở rộng chúng.

Thiếu phiên bản

Site đặt một phiên bản mà không vị trí quét nào tìm thấy sẽ lỗi với PHP_VERSION_MISSING kèm lệnh cài nó: brew install php@8.2 trên macOS; trên Linux là sudo apt install php8.2-cli (Debian và Ubuntu kèm một phiên bản, các bản khác lấy từ repository ondrej/php), sudo dnf install php82-php-cli (Fedora, từ Remi; nó cài /usr/bin/php82) hoặc yay -S php82 (Arch, từ AUR). Sau khi cài, Quét lại trong mục PHP (hoặc bất kỳ lệnh devkit nào) sẽ tìm ra nó.

Kiểm tra platform của composer

devkit php check 8.4 shop
shop with PHP 8.4: ok
  37 platform requirements met by PHP 8.4.19

php check chạy composer check-platform-reqs trong thư mục của site với PHP đó đứng đầu PATH và liệt kê những gì không đạt (thiếu extension, ràng buộc phiên bản) kèm package yêu cầu nó. Lệnh không thay đổi gì, nên nó trả lời câu "site này có chạy được trên 8.4 không?" trước khi bạn chuyển. devkit doctor <site> chạy cùng kiểm tra này cho PHP hiện tại của site.

Trong app

Mục PHP hiện ô PHP mặc định (php trong PATH hoặc một phiên bản, áp dụng ngay), mỗi binary trên máy là một thẻ (phiên bản, site, Xdebug, extension) và, với thẻ đang chọn, php.ini của nó (Mở php.ini, Sao chép đường dẫn), Xdebug hoặc cách cài nó, các site dùng nó, các extension đã nạp, và kết quả kiểm tra platform cho từng site đó. Kiểm tra site khác chạy kiểm tra cho bất kỳ site nào, đánh dấu (nếu chuyển sang). Phiên bản một site cần mà chưa được cài hiện thành thẻ chưa cài kèm lệnh cài và Sao chép lệnh; devkit không bao giờ tự chạy lệnh đó. Quét lại, Thêm binary… và danh sách Vị trí quét (bật/tắt, gỡ, Thêm vị trí, Đặt lại mặc định…) chỉnh nơi devkit tìm.

Xdebug

Các chế độ: off (không tốn thêm gì), debug (step debugging), coverage (cho phpunit --coverage), profile (file cachegrind trong output_dir của Xdebug). Xdebug phải được cài cho PHP đó; nếu không devkit báo XDEBUG_MISSING kèm lệnh cài (pecl install xdebug cạnh binary, hoặc php8.3-xdebug với apt, php83-php-pecl-xdebug3 với dnf, xdebug với pacman).

Site artisan: theo từng site và từng lane

devkit xdebug status shop
devkit xdebug on shop                    # debug
devkit xdebug on shop --mode coverage
devkit xdebug off shop
devkit xdebug on shop--checkout-fix      # lane có thiết lập riêng

devkit ghi một file ini nhỏ (trong ~/.config/devkit/xdebug/) với xdebug.mode, xdebug.start_with_request=trigger và xdebug.client_host=127.0.0.1, chỉ thêm nó vào thư mục scan PHP của web server và các process PHP của site, rồi chạy lại chúng. php.ini của bạn không bị đụng tới, và các site khác dùng cùng PHP không bị ảnh hưởng.

Site caddy: theo từng container PHP

PHP-FPM đọc chế độ một lần cho cả container, nên với caddy, Xdebug thuộc về container và ảnh hưởng mọi site và lane trên đó. Service compose phải truyền biến này vào:

    environment:
      XDEBUG_MODE: ${XDEBUG_MODE:-off}
devkit xdebug on shop                         # tạo lại container php83 với XDEBUG_MODE=debug
devkit xdebug container local-stack php83 --mode off
devkit php containers                         # từng service php, chế độ đang chạy, các site trên nó

devkit tạo lại service với XDEBUG_MODE mới, đọc lại chế độ từ container đang chạy, và chạy lại các process PHP của mọi site trên đó (kết quả liệt kê các site này). Về sau, nếu bạn tự chạy docker compose up -d cho stack đó, container sẽ được tạo lại với Xdebug tắt. Nơi Xdebug kết nối tới (xdebug.client_host) là thiết lập của chính image; với container thường là host.docker.internal.

Chỉ cho một lệnh

devkit exec shop --xdebug -- php vendor/bin/phpunit --filter CheckoutTest
XDEBUG_TRIGGER=1 devkit exec shop --xdebug -- php artisan orders:sync

--xdebug chạy PHP của lệnh này với xdebug.mode=debug,develop mà không đổi thiết lập của site. Khi có trigger, lần chạy sẽ kết nối tới IDE của bạn.

Kết nối IDE

Xdebug của devkit bắt đầu phiên khi có trigger: một request chỉ được debug khi nó mang trigger. Dùng extension trình duyệt như Xdebug Helper (nó đặt cookie XDEBUG_SESSION), thêm ?XDEBUG_TRIGGER=1 vào URL, hoặc đặt XDEBUG_TRIGGER=1 trong môi trường của lệnh CLI. Xdebug kết nối tới port 9003.

PhpStorm

  1. Settings → PHP → Debug: Xdebug port 9003 (mặc định).
  2. Bấm Start Listening for PHP Debug Connections trên thanh công cụ.
  3. Đặt breakpoint, bật trigger trong trình duyệt và tải trang. Ở lần kết nối đầu, PhpStorm hỏi các file thuộc dự án nào; chấp nhận.

VS Code: cài extension PHP Debug (xdebug.php-debug) và thêm vào .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    { "name": "Listen for Xdebug", "type": "php", "request": "launch", "port": 9003 }
  ]
}

Path mapping: không cần. Với artisan, PHP chạy trên máy bạn với cùng đường dẫn như editor. Với caddy, devkit yêu cầu container mount các site ở cùng đường dẫn tuyệt đối như trên máy, nên đường dẫn cũng khớp.

Trên Linux với Docker native, mặc định không có host.docker.internal. Thêm extra_hosts: ["host.docker.internal:host-gateway"] vào service PHP để Xdebug trong container gọi được tới IDE của bạn.

Trong app: menu … của site → Xdebug… liệt kê bốn chế độ, với debug thì hướng dẫn cách bắt đầu phiên (trigger, client host, port 9003, path mapping), cho biết thay đổi container ảnh hưởng tới site nào, và áp dụng bằng Áp dụng hoặc Áp dụng và chạy lại. Bảng PHP trong container của mục PHP đổi chế độ của một container trực tiếp, sau một sheet cho biết site nào bị ảnh hưởng.

HTTPS với engine caddy

Site caddy được phục vụ bởi một container Caddy từ một trong các stack của bạn, với PHP chạy trong container PHP-FPM đúng phiên bản của site. Bạn cần:

  1. mkcert đã cài (brew install mkcert hoặc package của distro bạn dùng).
  2. Tin cậy CA của nó một lần: devkit trust chạy mkcert -install, lệnh này có thể hỏi mật khẩu. Nó không làm gì nếu CA đã được tin cậy. Trong app: Cài đặt → Chứng chỉ → Tin cậy… mở lệnh trong terminal.
  3. Một stack có Caddy và PHP-FPM mount các site ở cùng đường dẫn. Một stack tối thiểu, cạnh stack database của bạn:
services:
  caddy:
    image: caddy:2
    environment:
      CADDY_ADMIN: 0.0.0.0:2019          # admin API, truy cập qua port được publish
    ports:
      - "443:443"                        # Docker Desktop không dùng được privileged port: "8443:443"
      - "127.0.0.1:2019:2019"            # chỉ loopback: devkit gửi chứng chỉ qua đây
    volumes:
      - ${HOME}/Project:${HOME}/Project  # các site của bạn, ở cùng đường dẫn
    networks: [default, webpress]
  php83:
    build: ./php83                       # FROM php:8.3-fpm kèm pdo_mysql, redis, intl, gd, zip, xdebug…
    labels:
      vn.webpress.devkit.role: php
      vn.webpress.devkit.php-version: "8.3"
    environment:
      XDEBUG_MODE: ${XDEBUG_MODE:-off}
    volumes:
      - ${HOME}/Project:${HOME}/Project
    networks: [default, webpress]
networks:
  webpress:
    external: true                       # network chứa database của bạn

với php83/Dockerfile:

FROM php:8.3-fpm
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/download/2.7.24/install-php-extensions /usr/local/bin/
RUN install-php-extensions pdo_mysql mysqli bcmath intl gd zip gmp pcntl sockets exif redis xdebug \
 && echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

Sau đó:

devkit stack add ~/Project/infra/web-stack
devkit site set shop --engine caddy --php 8.3
devkit site up shop                     # https://shop.localhost
devkit doctor shop

Điều xảy ra khi site up: devkit cấp chứng chỉ cho host, nạp nó vào Caddy qua admin API, và thêm một route cho host vào đầu server đang nghe :443 (hoặc server được đặt tên bằng label caddy-server; nếu không có, devkit tạo server riêng), chuyển PHP tới <php-service>:9000. site down, ẩn site, xoá lane hay xoá site sẽ gỡ route và chứng chỉ đi.

  • Caddy và service PHP có thể nằm ở stack khác với database. devkit tìm trong stack của site trước, rồi tới các stack khác.
  • Cả hai phải mount thư mục của site ở cùng đường dẫn tuyệt đối, nếu không sẽ lỗi SITE_NOT_MOUNTED. Service PHP phải gọi được database bằng tên service (dùng chung network).
  • Chạy lại Caddy sẽ làm mất các route của devkit. Khi đó các site hiện là đã dừng; site up đưa chúng trở lại.
  • Lệnh php của process, quick action và exec chạy trong container PHP ở cùng đường dẫn; pnpm và Vite chạy trên máy.
  • Admin API được tìm từ label caddy-admin, nếu không thì từ port 2019 được publish. Image caddy:2 cần CADDY_ADMIN: 0.0.0.0:2019, nếu không devkit báo CADDY_UNREACHABLE.

Chứng chỉ

devkit certs

Liệt kê CA của mkcert (đã được tin cậy chưa?) và mọi chứng chỉ devkit đã cấp, kèm host và ngày hết hạn. Lệnh không thay đổi gì. Chứng chỉ được lưu ở ~/.config/devkit/certs/<host>.pem và <host>-key.pem, mỗi host của site và lane một chứng chỉ (wildcard không phủ được crm-fix.core-development.localhost), và được cấp lại khi bị thiếu hoặc còn dưới 30 ngày là hết hạn. Trong app: Cài đặt → Chứng chỉ, với Tin cậy… và Kiểm tra lại.

Tên miền và /etc/hosts

  • .localhost (mặc định) không cần gì: trình duyệt và curl phân giải mọi tên *.localhost về chính máy bạn. Nên dùng nó.
  • .test hoặc hậu tố khác cần một dòng cho mỗi host trong /etc/hosts. devkit giữ chúng trong một khối có đánh dấu (# devkit begin … # devkit end) và chỉ ghi khi bạn yêu cầu:
devkit settings set --domain-suffix test
devkit hosts            # khối cần chứa gì, và chỗ nào khác
devkit hosts sync       # chỉ ghi lại khối của devkit, bằng sudo (hỏi mật khẩu một lần)
  • site up không bao giờ sửa /etc/hosts: nó cảnh báo khi thiếu tên, doctor cũng vậy. Quay về localhost rồi đồng bộ sẽ xoá khối này.
  • .local được phân giải qua mDNS (Bonjour trên macOS, Avahi trên Linux) và có thể làm mỗi lần tra tên chậm thêm vài giây. Tránh dùng nó.
  • Trong app: Cài đặt → Hosts cho biết khối có còn khớp với cấu hình hiện tại không, với Đồng bộ trong Terminal… (macOS) hoặc Đồng bộ trong terminal… (Linux) và Kiểm tra lại. App không bao giờ tự chạy lệnh; terminal sẽ hỏi mật khẩu của bạn.