~// notes/ thinkphp/ deploy-with-docker
ThinkPHP 8 · 后端框架

ThinkPHP8 从零到容器化部署:一份完整的上手指南

发布于 2026-07-08 阅读约 12 分钟 标签 #php #框架 #docker

ThinkPHP 8.0 是国内最受欢迎的 PHP 框架之一,它以「轻量、约定优于配置」著称, 8.0 版本在保持简洁风格的同时,全面拥抱了 PHP 8.0+ 的强类型、注解和纤程等新特性。 本文将带你从零开始,搭建一个能跑、能测、能部署的 ThinkPHP8 项目。

本指南包含 环境要求 · 目录规范 · 路由与中间件 · ORM · 鉴权 · Docker Compose 部署 · 常见坑位;可直接复制运行。

环境要求

在开始之前,请确保本机或服务器满足以下最低配置:

  • PHP ≥ 8.0(推荐 8.2,可获得完整的 readonly / true / false 类型支持)
  • Composer ≥ 2.4
  • MySQL 5.7+ / PostgreSQL 13+ / SQLite 3
  • Node.js ≥ 18(仅在使用前端构建链时需要)
  • Docker ≥ 24(部署阶段使用)

PHP 扩展清单

ThinkPHP 8 默认依赖以下扩展,开启它们即可:

bash
# Debian / Ubuntu
sudo apt-get install -y php8.2 \
    php8.2-cli php8.2-fpm php8.2-mysql php8.2-redis \
    php8.2-mbstring php8.2-xml php8.2-curl php8.2-zip \
    php8.2-bcmath php8.2-sockets php8.2-opcache

# 验证扩展
php -m | grep -E "pdo|mbstring|openssl|tokenizer|xml|ctype|json|bcmath|fileinfo|curl|sockets"

创建项目

使用 Composer 创建 ThinkPHP8 项目只需要一行命令:

bash
composer create-project topthink/think blog-app
cd blog-app

# 启动内置服务器验证
php think run

# 默认监听 http://127.0.0.1:8000

启动成功后,浏览器打开 http://127.0.0.1:8000,如果能看到「ThinkPHP 迎你」字样, 那么环境就完全 OK 了。

目录规范

ThinkPHP8 采用「多应用 + 单入口」结构,下面是我个人比较推荐的组织方式:

目录作用是否提交
app/应用代码(控制器、模型、中间件)
config/配置(数据库、缓存、路由等)
route/路由定义
public/Web 入口、静态资源
runtime/运行时缓存 / 日志
vendor/Composer 依赖
.env本地环境变量
请务必把以下加入 .gitignore runtime/vendor/.env*.log,否则仓库会越来越臃肿,也会泄露密钥。

路由与中间件

TP8 推荐用「路由注解」声明接口,开发体验非常接近 Spring Boot:

php · app/controller/Post.php
namespace app\controller;

use app\model\Post as PostModel;
use think\annotation\Route;
use think\response\Json;

class Post
{
    #[Route('GET', 'posts', 'index')]
    public function index(): Json
    {
        $list = PostModel::with(['author', 'tags'])
            ->order('id', 'desc')
            ->paginate(10);

        return json([
            'code' => 0,
            'data' => $list,
        ]);
    }

    #[Route('POST', 'posts', 'save')]
    public function save(): Json
    {
        $data = input();
        $post = PostModel::create($data);
        return json(['code' => 0, 'data' => $post]);
    }
}

用中间件统一处理 JWT 鉴权

把所有「控制器之前要做的事」放进中间件,可以让控制器保持纯粹:

php · app/middleware/Auth.php
namespace app\middleware;

use think\facade\Jwt;
use think\response\Json;

class Auth
{
    public function handle($request, \Closure $next)
    {
        $token = $request->header('Authorization');
        if (!$token || !$uid = Jwt::verify($token)) {
            return json(['code' => 401, 'msg' => '未登录'])->code(401);
        }

        $request->uid = $uid;
        return $next($request);
    }
}

route/app.php 中按需挂载:

php · route/app.php
use app\middleware\Auth;

Route::group('api', function () {
    Route::get('posts', 'Post/index');
    Route::post('posts', 'Post/save')->middleware(Auth::class);
})->middleware(\app\middleware\Cors::class);

数据库与 ORM

TP8 的 ORM(基于 think-orm)使用起来非常顺手,下面展示一个「用户 → 文章 → 评论」的三表关系:

php · app/model/User.php
namespace app\model;

use think\Model;

class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class, 'user_id');
    }

    public function profile()
    {
        return $this->hasOne(UserProfile::class, 'user_id');
    }
}

预加载(with())是 N+1 查询的特效药。一次 JOIN 拉完所有数据,比循环里再查一遍快 10~100 倍。

Docker 容器化部署

本地跑通只是第一步。生产环境我们需要把 PHP、Nginx、MySQL、Redis 一起编排起来。 下面是一份可直接运行的 docker-compose.yml

yaml · docker-compose.yml
version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: docker/Dockerfile
    volumes:
      - ./:/var/www/html
    depends_on:
      mysql: { condition: service_healthy }
      redis: { condition: service_healthy }
    networks: [app-net]

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./public:/var/www/html/public:ro
    depends_on: [app]
    networks: [app-net]

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd
      MYSQL_DATABASE: blog
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
    networks: [app-net]

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
    networks: [app-net]

volumes:
  mysql_data:

networks:
  app-net:
    driver: bridge

多阶段 Dockerfile

用一个 Dockerfile 跑 Composer 装依赖,再把 vendor 拷到运行时镜像,最终镜像只有 ~120MB:

dockerfile · docker/Dockerfile
# 阶段一:安装依赖
FROM composer:2.7 AS vendor
WORKDIR /app
COPY composer.* ./
RUN composer install --no-dev --no-scripts --prefer-dist
COPY . .
RUN composer dump-autoload --optimize

# 阶段二:运行时
FROM php:8.2-fpm-alpine
RUN docker-php-ext-install pdo pdo_mysql bcmath opcache
WORKDIR /var/www/html
COPY --from=vendor /app /var/www/html
RUN chown -R www-data:www-data /var/www/html

EXPOSE 9000
CMD ["php-fpm"]

Nginx 配置

把请求全部转发到 app:9000,静态资源由 Nginx 直接返回:

nginx · docker/nginx.conf
server {
    listen 80;
    server_name _;
    root /var/www/html/public;
    index index.php;

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

    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

一键启动

所有文件就绪后,在项目根目录执行:

bash
# 启动全部服务(后台)
docker compose up -d --build

# 查看运行状态
docker compose ps

# 进入容器执行命令
docker compose exec app php think migrate:run

# 实时查看日志
docker compose logs -f app
下一步建议 docker-compose.yml 接入 GitHub Actions,推送到自建 Harbor 镜像仓库,再用 Watchtower 自动滚动更新,整条 CI/CD 就闭环了。

小结

本篇用一个完整的示例,串起了 ThinkPHP8 项目从环境准备、目录规划、路由 ORM,到容器化部署的全过程。 核心思路其实就是三件事:

  1. composer 把依赖管好,用 .env 把配置隔离
  2. 路由注解 + 中间件 让控制器保持纯粹
  3. 多阶段构建 + Compose 把运行环境固化下来

希望这份笔记能帮你少踩几个坑。如果你有更好的实践,欢迎在评论区或邮件与我交流 🚀