当前位置: 首页 > news >正文

Mac上用Docker运行宝塔面板的LNMP开发环境搭建指南

1. 项目概述:为什么在 Mac 上用 Docker 跑宝塔不是“炫技”,而是真实存在的刚需场景

Mac 是开发者的主力工作机,但 macOS 原生不支持 systemd、没有标准的 init 系统,传统 Linux 发行版上开箱即用的宝塔面板(Bt-Panel)根本无法直接安装运行。你搜“mac 安装宝塔”出来的结果,90% 是“不支持”“别折腾了”“换虚拟机吧”——这话没错,但错在它默认把“不能原生装”等同于“完全不可用”。而现实是:很多前端工程师要本地联调 PHP 后端接口,独立开发者需要快速验证 LNMP 站点在生产环境的表现,甚至运维同学想在自己 MacBook 上复现客户服务器的宝塔配置逻辑……这些需求不会因为你换台 Windows 电脑就消失。Docker 正是那个绕过系统限制的“合法后门”:它不依赖宿主机内核服务,只借个 Linux 容器空间;宝塔官方虽未适配 macOS,但它的核心是 Python + Shell + Nginx/PHP/MySQL 服务进程,而这三样,Docker 镜像里全都有。我试过三种路径——Parallels 虚拟机装完整 CentOS、Vagrant + VirtualBox 搭建最小化环境、纯 Docker 容器化部署,最终选 Docker 不是因为它最“高级”,而是它启动最快(30 秒内)、资源占用最低(内存常驻 450MB 左右)、环境最干净(删容器即清空,不留任何残留配置),且能和本地 VS Code、Postman、Chrome 完全无缝协作。关键词“Mac, Docker, 宝塔, LNMP”背后的真实诉求,从来不是“在 Mac 上假装自己是服务器”,而是“用 Mac 的开发体验,获得和线上服务器一致的运维界面与调试环境”。这不是妥协,是精准匹配——就像你不会为了写 Python 脚本去装一台 Ubuntu 虚拟机,你直接用 pyenv + VS Code;同理,LNMP 环境调试,Docker 就是那个最轻量、最可控、最可复现的“脚手架”。

2. 整体设计思路:为什么不用官方宝塔镜像,而坚持自建 LNMP + 宝塔组合?

很多人看到标题第一反应是:“宝塔不是有 Docker 镜像吗?直接 docker run 不就完了?”——这是最大的认知陷阱。我实测过至少 7 个标榜“宝塔 Docker”的公开镜像(包括 GitHub 上 star 过千的几个),全部踩坑:有的镜像基于老旧的 Ubuntu 16.04,PHP 版本卡在 7.2,连 Laravel 9 都跑不起来;有的把 MySQL 和 Nginx 打包进同一个容器,违反“一个容器一个进程”原则,导致日志无法分离、端口冲突频发;更离谱的是,有镜像把宝塔面板的 web 界面和后台服务硬编码成监听 0.0.0.0:8888,结果在 Mac 上启动后,你根本打不开 http://localhost:8888,因为 Docker Desktop for Mac 的网络模型和 Linux 主机不同,它走的是虚拟机桥接,不是直通。所以我的方案彻底放弃“拿来主义”,采用分层构建策略:底层用官方mysql:8.0php:8.1-apachenginx:alpine镜像,确保基础服务稳定、安全更新及时;中间层用 Docker Compose 编排网络与卷挂载,让 Nginx 反向代理 PHP-FPM、PHP 连接 MySQL 形成标准 LNMP 链路;顶层单独起一个轻量容器,只装宝塔的 Web 管理前端(bt-panel),它不管理任何服务进程,只通过 API 调用宿主机(即 Compose 中的其他容器)暴露的管理端口。这个设计的核心逻辑是:宝塔的价值在于可视化操作习惯和配置模板,而不是它自带的服务进程。你本地开发时,真正需要调试的是 PHP 代码、MySQL 查询、Nginx rewrite 规则,这些必须由标准、可控、可 debug 的官方镜像提供;宝塔只是你操作它们的“遥控器”。这样做的好处是显而易见的:升级 PHP 只需改一行image: php:8.2-apache,换 MySQL 版本只需改mysql:8.1,所有变更都清晰可见、可回滚;而宝塔面板本身,哪怕某天它更新出 bug,你删掉面板容器重建一个,LNMP 底层服务毫发无损。这比“一个镜像包打天下”的黑盒方案,可靠度高出不止一个数量级。

2.1 为什么坚持用 Alpine 版 Nginx 而非 Ubuntu/Debian 基础镜像?

Nginx 官方提供了nginx:alpinenginx:slimnginx:bullseye多个变体,我在 Mac 上实测对比了三者启动耗时、内存占用、体积大小:

镜像标签首次拉取体积启动后内存占用启动时间(冷启动)是否含 curl/wget
nginx:alpine15.2 MB12.4 MB0.8s否(需 apk add)
nginx:slim48.7 MB28.1 MB1.3s是(busybox 版)
nginx:bullseye132 MB41.6 MB2.1s

数据很直观:Alpine 镜像体积不到 Bullseye 的八分之一,内存占用低一半以上。Mac 用户对资源敏感度极高——你同时开着 Chrome(10+ 标签页)、VS Code(3 个工程)、Docker Desktop、Figma,再塞一个 132MB 的 Nginx 容器,Swap 开始疯狂抖动是必然的。Alpine 的精简不是牺牲功能,而是剔除所有非必要组件:它用 musl libc 替代 glibc,用 busybox 替代 GNU coreutils,启动更快、攻击面更小。唯一代价是某些 Linux 命令(如ifconfignetstat)默认没有,但这对 Nginx 来说毫无影响——你查端口用ss -tuln,看日志用tail -f /var/log/nginx/access.log,全部原生支持。更重要的是,Alpine 的包管理apk极其轻快,添加一个curl只需apk add --no-cache curl,执行时间 < 0.3s,远快于 apt-get update && apt-get install 的冗长流程。我曾为调试反向代理加过curl,整个过程从修改 Dockerfile 到容器重启生效,全程 8 秒搞定。这种“秒级响应”对本地开发节奏至关重要——你不想为查一个 header 多等 15 秒。所以,选择 Alpine 不是跟风,是在 Mac 这个特定硬件平台上,对性能、资源、响应速度三者做的最优解。

2.2 为什么宝塔面板容器必须与 LNMP 服务容器网络隔离,仅通过 API 通信?

这是整个方案最反直觉、也最关键的设计。常规思维会觉得:“宝塔要管 Nginx、PHP、MySQL,那它肯定得和它们在同一个 Docker 网络里,甚至共享网络命名空间才方便。” 错。Docker 的网络模型决定了:如果宝塔容器和 Nginx 容器共用--network container:nginx,那么宝塔容器将完全失去自己的网络栈,它无法监听 8888 端口(因为端口被 Nginx 占了),也无法被 Mac 主机访问(因为网络被“借走”了)。而如果放在同一个自定义 bridge 网络里,看似可行,实则埋雷:宝塔面板的安装脚本(install.sh)会自动探测本机 IP 并写入配置,它探测到的是 Docker 内部的 172.x.x.x 地址,而非localhost,导致你在浏览器输入 http://localhost:8888 时,页面加载的 JS/CSS 资源地址却是http://172.18.0.3:8888/static/xxx.js,Mac 主机根本无法解析这个内网 IP,页面白屏。我的解法是物理隔离:宝塔容器使用host网络模式(network_mode: "host"),让它直接绑定 Mac 主机的 8888 端口;LNMP 服务容器则放在独立的lnmp-net自定义网络中,彼此通过容器名通信(如 PHP 容器连mysql:3306)。宝塔面板不直接操作服务,而是通过一套轻量 API 层——我用 Python 写了一个 200 行的bt-api-proxy服务,部署在 LNMP 网络内,它监听 8080 端口,接收宝塔发来的/api/start_nginx/api/restart_php等请求,再用docker exec命令去操作对应容器。这样,宝塔看到的永远是http://localhost:8080/api/xxx,Mac 主机能正常访问;LNMP 服务在纯净网络中运行,不受宝塔脚本干扰;API 代理层成了可控的“翻译官”,把宝塔的“人话”转成 Docker 的“机器指令”。这个设计让我规避了所有公开宝塔镜像的网络顽疾,也是它能在 Mac 上真正可用的底层保障。

3. 核心细节解析:从零开始搭建可落地的 LNMP + 宝塔环境

搭建过程分为四个明确阶段:环境准备 → LNMP 底层服务部署 → 宝塔面板容器化 → API 代理层对接。每个阶段都有不可跳过的细节,漏掉任意一步,轻则功能缺失,重则整个环境无法访问。下面我按实际操作顺序,把每一步的命令、配置、原理、避坑点全部摊开讲透。

3.1 环境准备:Docker Desktop 配置与 Mac 系统级优化

Mac 上跑 Docker,第一步不是写 Dockerfile,而是确保 Docker Desktop 本身处于最佳状态。很多人忽略这点,导致后续所有操作都慢半拍甚至失败。

首先,确认 Docker Desktop 已启用Virtualization Framework(不是旧版的 HyperKit)。打开 Docker Desktop 设置 → General → 勾选 “Use the new Virtualization framework”,这是 Apple Silicon(M1/M2/M3)芯片的必备选项,能提升 30% 以上 I/O 性能。如果你用 Intel Mac,此项灰色不可选,无需理会。

其次,调整资源分配。默认设置(2 CPU / 2GB RAM)对 LNMP 完全不够:MySQL 8.0 启动就要 512MB,PHP-FPM 开 4 个 worker 进程约 300MB,Nginx 再占 100MB,光服务就超 1GB。进入 Settings → Resources → Memory,务必调到 4GB 起步;CPU 调到 3-4 核。别怕“占资源”,Docker Desktop 的内存是动态分配的,空闲时会自动释放,只有当你真在跑高负载服务时才吃满。

第三,关键一步:关闭 Docker Desktop 的File Sharing中的/Users全盘共享。默认它会把整个用户目录挂载进虚拟机,导致文件变更通知(inotify)失效,PHP 的 opcache 无法热更新,你改完代码刷新页面还是旧的。正确做法是:Settings → Resources → File Sharing → 点击+号,只添加你的项目根目录(如/Users/yourname/dev/my-laravel-app),并确保勾选 “Use gRPC FUSE for file sharing”。这个选项开启后,文件同步延迟从秒级降到毫秒级,PHP 代码修改保存后,1 秒内就能在浏览器看到效果。

提示:执行完上述设置后,必须点击右下角 “Apply & Restart” 重启 Docker Desktop,否则配置不生效。我见过太多人改完设置没重启,然后花两小时排查“为什么 PHP 不热更新”。

最后,验证基础环境。打开终端,执行:

docker --version # 应输出 Docker version 24.x.x, build xxxxx docker-compose --version # 应输出 docker-compose version 2.2x.x docker run --rm hello-world # 必须看到 "Hello from Docker!",证明引擎正常

如果docker-compose命令报错,说明你用的是新版 Docker Desktop(v2.20+),它已将docker-compose命令集成进docker compose(注意中间有空格)。此时所有docker-compose up都要改成docker compose up。这是 Docker 官方的强制迁移,不是你的环境问题。

3.2 LNMP 底层服务部署:编写可复用的 docker-compose.yml

LNMP 的三个服务(Nginx、PHP、MySQL)必须通过docker-compose.yml统一编排,这是保证它们网络互通、卷挂载一致、启动顺序可控的唯一可靠方式。下面是我经过 17 次迭代打磨出的生产级配置,每一行都有明确目的:

# docker-compose.yml version: '3.8' services: # MySQL 服务:数据持久化是生命线 mysql: image: mysql:8.0 container_name: lnmp-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: myapp MYSQL_USER: appuser MYSQL_PASSWORD: app123 volumes: - ./data/mysql:/var/lib/mysql:rw - ./conf/mysql/my.cnf:/etc/mysql/my.cnf:ro ports: - "3306:3306" networks: - lnmp-net command: --default-authentication-plugin=mysql_native_password # PHP 服务:Apache 已淘汰,专注 FPM 模式 php: image: php:8.1-fpm-alpine container_name: lnmp-php restart: unless-stopped volumes: - ./www:/var/www/html:rw,cached - ./conf/php/php.ini:/usr/local/etc/php/php.ini:ro - ./conf/php/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro networks: - lnmp-net depends_on: - mysql # Nginx 服务:Alpine + 自定义配置 nginx: image: nginx:alpine container_name: lnmp-nginx restart: unless-stopped volumes: - ./www:/var/www/html:rw,cached - ./conf/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./conf/nginx/conf.d:/etc/nginx/conf.d:ro - ./logs/nginx:/var/log/nginx:rw ports: - "80:80" - "443:443" networks: - lnmp-net depends_on: - php networks: lnmp-net: driver: bridge ipam: config: - subnet: 172.20.0.0/16

这份配置的精华在细节里:

  • volumes中的:cached选项是 Mac 性能杀手锏。它告诉 Docker Desktop 使用 macOS 的 FUSE 文件系统缓存,避免每次文件读写都穿透到虚拟机,PHP 加载 Composer autoload 文件的速度能提升 5 倍。没有它,composer install可能卡住 2 分钟。
  • MySQL 的command: --default-authentication-plugin=mysql_native_password是为兼容老项目。MySQL 8.0 默认用caching_sha2_password,但很多 PHP PDO 驱动(尤其旧版)不支持,连不上数据库。加这行就降级回经典认证,一劳永逸。
  • PHP 容器不暴露任何端口,因为它只通过 Unix Socket 或fastcgi_pass php:9000被 Nginx 调用。depends_on确保 PHP 在 MySQL 之后启动,避免 PHP 应用启动时连不上数据库报错。
  • Nginx 的conf.d目录挂载为只读(:ro),防止容器内误删配置;logs/nginx挂载为读写(:rw),方便你在 Mac 主机上直接tail -f logs/nginx/access.log查日志,不用进容器。

执行docker compose up -d后,用docker compose ps查看状态,三个容器都应为healthyrunning。此时访问http://localhost,应该能看到 Nginx 默认欢迎页——LNMP 底层已就绪。

3.3 宝塔面板容器化:剥离服务管理,只留 Web 界面

现在轮到宝塔。我们不运行它的完整安装脚本,而是提取其 Web 前端静态文件,用一个极简的 Python HTTP 服务器承载。原因前面说过:避免它污染 LNMP 网络、避免它尝试管理宿主机服务。

第一步,获取宝塔 Web 前端。访问宝塔官网(https://www.bt.cn),下载最新版 Linux 安装包(如bt.tar.gz),解压后进入panel目录,你会看到static/templates/application/等文件夹。我们需要的只是static(JS/CSS/图片)和templates(HTML 模板)。把这两个文件夹复制到你的项目目录下,结构如下:

my-lnmp/ ├── docker-compose.yml ├── www/ ├── conf/ ├── data/ ├── logs/ ├── bt-web/ # 新建目录 │ ├── static/ │ └── templates/

第二步,写一个Dockerfile.bt,内容极其简单:

FROM python:3.9-slim WORKDIR /app COPY bt-web/ . EXPOSE 8888 CMD ["python3", "-m", "http.server", "8888"]

它做的事就是:用 Python 内置的http.server模块,把bt-web目录当 Web 根目录,监听 8888 端口。没有 Nginx,没有 Apache,没有宝塔复杂的后台进程,纯粹就是一个文件服务器。

第三步,在docker-compose.yml中加入宝塔服务:

# 宝塔面板:纯静态文件服务 bt-panel: build: context: . dockerfile: Dockerfile.bt container_name: bt-panel restart: unless-stopped network_mode: "host" # 关键!绑定主机网络 volumes: - ./bt-web:/app:ro

执行docker compose up -d bt-panel,稍等几秒,打开http://localhost:8888。你应该能看到宝塔熟悉的蓝色登录页——但此时它是个“哑巴”:点击登录,输入默认账号admin/admin,进去后所有服务状态都是灰色的,因为没连上 API。这正是我们想要的状态:界面有了,控制权还在我们手里。

注意:首次访问可能提示“此网站无法提供安全连接”,这是因为宝塔前端 JS 尝试用 HTTPS 加载资源,但我们用的是 HTTP 服务器。解决方法是在浏览器地址栏输入http://localhost:8888后,按Cmd+Shift+C打开开发者工具,切换到 Console 标签页,粘贴执行以下代码强制禁用 HTTPS 重定向:

localStorage.setItem('bt_ssl', '0'); location.reload();

执行后刷新页面,即可正常进入。

3.4 API 代理层对接:用 200 行 Python 实现宝塔与 Docker 的对话

现在,宝塔界面和 LNMP 服务是割裂的。我们要搭一座桥,让宝塔界面上的“重启 Nginx”按钮,真的能执行docker restart lnmp-nginx。这个桥就是bt-api-proxy

创建api-proxy/目录,放入main.py

#!/usr/bin/env python3 # api-proxy/main.py import os import subprocess import json from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import urlparse, parse_qs class BTAPIHandler(BaseHTTPRequestHandler): def do_POST(self): if self.path == '/api/start_nginx': self._exec_docker_cmd(['docker', 'start', 'lnmp-nginx']) elif self.path == '/api/stop_nginx': self._exec_docker_cmd(['docker', 'stop', 'lnmp-nginx']) elif self.path == '/api/restart_nginx': self._exec_docker_cmd(['docker', 'restart', 'lnmp-nginx']) elif self.path == '/api/start_php': self._exec_docker_cmd(['docker', 'start', 'lnmp-php']) elif self.path == '/api/restart_php': self._exec_docker_cmd(['docker', 'restart', 'lnmp-php']) elif self.path == '/api/start_mysql': self._exec_docker_cmd(['docker', 'start', 'lnmp-mysql']) elif self.path == '/api/restart_mysql': self._exec_docker_cmd(['docker', 'restart', 'lnmp-mysql']) else: self.send_error(404, "API not found") def _exec_docker_cmd(self, cmd): try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) if result.returncode == 0: self._send_json({"status": True, "msg": "success"}) else: self._send_json({"status": False, "msg": result.stderr.strip()}) except Exception as e: self._send_json({"status": False, "msg": str(e)}) def _send_json(self, data): self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(data).encode()) if __name__ == '__main__': server = HTTPServer(('0.0.0.0', 8080), BTAPIHandler) print("BT API Proxy listening on port 8080...") server.serve_forever()

再写一个Dockerfile.api

FROM python:3.9-slim WORKDIR /app COPY api-proxy/ . RUN pip install --no-cache-dir requests EXPOSE 8080 CMD ["python3", "main.py"]

最后,在docker-compose.yml中加入 API 代理服务:

# API 代理:宝塔与 Docker 的翻译官 bt-api-proxy: build: context: . dockerfile: Dockerfile.api container_name: bt-api-proxy restart: unless-stopped networks: - lnmp-net volumes: - /var/run/docker.sock:/var/run/docker.sock:ro # 关键!挂载 Docker socket

注意volumes中挂载的/var/run/docker.sock:这是 Docker 的“控制权凭证”,容器内程序通过它可以直接调用宿主机的 Docker Daemon。没有它,subprocess.run(['docker', ...])就是废代码。

执行docker compose up -d bt-api-proxy,然后测试 API:

curl -X POST http://localhost:8080/api/restart_nginx # 应返回 {"status": true, "msg": "success"}

现在,回到宝塔界面(http://localhost:8888),打开浏览器开发者工具(F12),切换到 Network 标签页,点击界面上的“重启 Nginx”按钮。你会看到一个POST /api/restart_nginx请求发出,响应是{"status":true},同时lnmp-nginx容器的日志里会刷出 Nginx 重启信息。至此,宝塔不再是摆设,它真正拥有了“遥控”能力。

4. 实操过程详解:从初始化到日常开发的完整工作流

上面的配置是骨架,真正让它活起来的是日常怎么用。我把整个生命周期拆成四个典型场景:初始化部署、本地开发联调、数据库管理、环境备份与迁移。每个场景都给出可直接复制粘贴的命令和注意事项。

4.1 初始化部署:5 分钟完成从零到可访问

这是你第一次搭建的全流程,务必按顺序执行,跳步会导致环境不一致。

步骤 1:创建项目目录并初始化结构

mkdir my-lnmp && cd my-lnmp mkdir -p www conf/nginx/conf.d conf/php conf/mysql data/mysql logs/nginx bt-web/api-proxy

步骤 2:写入核心配置文件

  • conf/nginx/nginx.conf:粘贴标准 Nginx 全局配置(worker_processes、events、http 块),重点是include /etc/nginx/conf.d/*.conf;
  • conf/nginx/conf.d/default.conf:写你的站点配置,关键行:
    location ~ \.php$ { fastcgi_pass php:9000; # 注意:指向容器名 php,不是 localhost fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name; }
  • conf/php/php.ini:启用extension=mysqliextension=pdo_mysql,设置date.timezone = Asia/Shanghai
  • conf/mysql/my.cnf:写[mysqld]段,加character-set-server=utf8mb4collation-server=utf8mb4_unicode_ci

步骤 3:启动 LNMP 底层

docker compose up -d mysql php nginx # 等待 30 秒,执行 docker exec lnmp-mysql mysql -uappuser -papp123 -e "CREATE DATABASE IF NOT EXISTS testdb;" # 验证:curl http://localhost 返回 Nginx 页,curl http://localhost/test.php(放个 phpinfo())返回 PHP 信息

步骤 4:启动宝塔与 API 代理

# 先启动 API 代理(它依赖 Docker socket) docker compose up -d bt-api-proxy # 再启动宝塔面板 docker compose up -d bt-panel # 访问 http://localhost:8888,执行 Console 里的 localStorage 命令解除 HTTPS 强制

步骤 5:在宝塔界面完成最后对接登录宝塔(admin/admin)→ 点击左上角“首页” → 在“服务状态”区域,找到 Nginx、PHP、MySQL 三项 → 点击右侧“设置”图标 → 在弹出窗口中,“Web 服务”填http://localhost:8080/api,“PHP 服务”填http://localhost:8080/api,“数据库服务”填http://localhost:8080/api。保存后,状态灯会由灰变绿,表示对接成功。

实操心得:第一次对接失败,90% 是因为bt-api-proxy容器没起来,或者docker.sock挂载失败。用docker logs bt-api-proxy查看日志,如果出现Permission denied,说明挂载的 socket 文件权限不对,执行sudo chmod 666 /var/run/docker.sock(仅限开发环境,生产勿用)。

4.2 本地开发联调:如何让http://localhost直接访问你的 Laravel/WordPress 项目

LNMP 搭好了,但你的代码在哪?www/目录就是你的 Web 根目录。Mac 开发者习惯用 VS Code 直接编辑代码,所以www/必须是你项目的实际位置。

假设你的 Laravel 项目在/Users/yourname/dev/my-laravel,那么:

# 删除默认的 www 目录,创建符号链接 rm -rf www ln -s /Users/yourname/dev/my-laravel www

这样,www/就是你的项目,VS Code 打开my-lnmp目录,就能看到完整的 Laravel 结构。

但 Laravel 需要.env配置数据库:

DB_CONNECTION=mysql DB_HOST=lnmp-mysql # 注意:这里是容器名,不是 localhost! DB_PORT=3306 DB_DATABASE=myapp DB_USERNAME=appuser DB_PASSWORD=app123

然后,在宝塔界面 → 网站 → 添加站点 → 域名填localhost,根目录选/www,PHP 版本选81。添加后,宝塔会自动生成 Nginx 配置到conf/nginx/conf.d/localhost.conf,你无需手动改。

此时访问http://localhost,Laravel 的欢迎页就出来了。如果遇到500 Internal Server Error,立刻查logs/nginx/error.log,90% 是权限问题:Laravel 的storage/bootstrap/cache/目录需要www-data用户(Nginx 容器内用户)可写。在 Mac 终端执行:

docker exec lnmp-php chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

这条命令必须在每次git pull新代码后执行一次,因为 Git 拉下来的文件属主是你的 Mac 用户,不是www-data

实操心得:WordPress 用户注意,它的wp-config.php里数据库主机必须写lnmp-mysql,写localhost127.0.0.1都会连不上,因为那是容器内部的 localhost,不是 MySQL 容器的地址。

4.3 数据库管理:不用命令行,用宝塔图形化操作 MySQL

宝塔的价值在此刻体现:你不再需要记mysql -h lnmp-mysql -uappuser -papp123 myapp这串命令。在宝塔界面 → 数据库 → 添加数据库,填入:

  • 数据库名:myapp
  • 用户名:appuser(自动生成也可,但建议用已知密码的)
  • 密码:app123
  • 权限:localhost(因为 PHP 容器名是php,它连 MySQL 时 host 就是localhost

点击提交,宝塔会自动执行 SQL 创建数据库和用户。然后点击“管理”按钮,它会跳转到 phpMyAdmin 界面(宝塔内置),你可以用鼠标点点点,导入 SQL 文件、执行查询、管理表结构,和线上服务器体验完全一致。

但有一个隐藏技巧:phpMyAdmin 默认上传文件大小限制是 2MB,导出的大 SQL 文件传不上去。解决方法是,在conf/php/php.ini里加两行:

upload_max_filesize = 128M post_max_size = 128M

然后重启 PHP 容器:docker restart lnmp-php。重启后,phpMyAdmin 的上传限制就变成 128MB 了。

注意:宝塔的“phpMyAdmin”是它自己打包的版本,不是官方最新版。如果你需要最新特性(如 Dark Mode),可以卸载宝塔自带的,换成官方镜像。在docker-compose.yml中加一个服务:

phpmyadmin: image: phpmyadmin/phpmyadmin container_name: pma restart: unless-stopped environment: PMA_HOST: lnmp-mysql PMA_PORT: 3306 ports: - "8081:80" networks: - lnmp-net

然后访问http://localhost:8081,就是纯净的官方 phpMyAdmin。

4.4 环境备份与迁移:如何把整套 LNMP + 宝塔环境迁移到另一台 Mac

Docker 的最大优势是可移植性。备份不是导出镜像,而是备份你的“配置即代码”(Infrastructure as Code)。

备份清单(只需这 4 项):

  1. docker-compose.yml文件(核心编排)
  2. conf/目录(所有服务配置)
  3. data/mysql/目录(MySQL 数据文件,/var/lib/mysql的完整快照)
  4. bt-web/目录(宝塔前端静态文件)

执行备份命令:

# 打包配置和数据 tar -czf my-lnmp-backup-$(date +%Y%m%d).tar.gz \ docker-compose.yml \ conf/ \ data/mysql/ \ bt-web/

恢复流程:

  1. 在新 Mac 上安装 Docker Desktop,完成 3.1 节的环境配置。
  2. 解压备份包:tar -xzf my-lnmp-backup-20240501.tar.gz
  3. 启动服务:docker compose up -d
  4. 如果 MySQL 启动失败,大概率是data/mysql/权限问题。执行:
    sudo chown -R 999:999 data/mysql/ # MySQL 容器内用户 UID 是 999 docker restart lnmp-mysql

整个过程 3 分钟内完成,且环境 100% 一致。你不需要在新机器上重新下载镜像(Docker 会自动拉取),也不需要重新配置 PHP 参数(conf/php/php.ini已包含)。

实操心得:不要备份www/目录!它是你的代码,应该用 Git 管理。www/是符号链接或空目录,备份它只会增大包体积,且造成混淆。真正的“环境”是配置和数据,代码是“内容”,二者必须分离。

5. 常见问题与排查技巧实录:那些文档里不会写的坑

以下是我在 32 个项目中踩过的、最典型、最高频的 7 个问题,附带真实日志、排查路径和一招解决的命令。这些问题,99% 的新手都会遇到,但网上教程几乎从不提。

5.1 问题:宝塔登录页空白,Console 报错Failed to load resource: net::ERR_CONNECTION_REFUSED

现象描述:
访问http://localhost:8888,页面一片空白,F12 打开 Console,看到大量GET http://localhost:8888/static/css/app.css net::ERR_CONNECTION_REFUSED错误。

根本原因:
宝塔前端 JS 尝试用 `http://localhost

http://www.jsqmd.com/news/1192060/

相关文章:

  • 从C4996错误到安全编程:深入解析Visual Studio中scanf的弃用与替代方案
  • [Dify实战] 为什么很多团队不是不会做 AI,而是没有先搭好可复用的 Dify 入口层?
  • 影刀RPA 密码安全管理:凭证加密存储与安全调用
  • 跨端支付实战:利用SDK将APP支付参数转换为H5链接
  • 【MOTOTRBO】 CPS2.0许可证注册与模拟信道配置详解
  • 遗传算法落地实战:自适应参数与工业级编码设计
  • 如何在 Edge 浏览器中配置企业级 IE 模式站点列表
  • N皇后遗传算法实战:从Matlab到Python的编码、适应度与收敛深度解析
  • 树莓派3 HDMI转VGA黑屏的硬件-固件协同故障解析
  • OpenGL 实例化渲染:构建小行星带 (Asteroid Field)
  • 大脑、肌肉与粮草:CPU、GPU、内存、显存的爱恨情仇
  • HarmonyOS APP实战-基于Image Kit的图像处理APP - 第5篇:图片旋转与翻转
  • 【C#】Windows服务(Service)自动化部署与静默管理方案
  • [AI] 这周最值得继续追的 3 个 AI / Dify 热点,到底哪一个最可能先落地?
  • AI聚合工具:KULAAI如何破局?及KULAAI镜像站实战指南
  • Go环境变量配置完全指南:GOROOT、GOPATH、GOPROXY
  • 车载视觉系统长距离视频传输:TI DS90UH940N-Q1解串器原理与应用
  • yuzu模拟器中文显示深度调优方案:三步实现专业级视觉体验
  • 基于MATLAB的智能车牌识别系统:从图像预处理到字符识别的完整实现
  • MATLAB_数字信号处理_模拟滤波器_巴特沃斯滤波器设计实战:从理论到代码实现
  • C++编译错误深度解析:从“未声明的标识符”到工程化实践
  • HarmonyOS APP实战-基于Image Kit的图像处理APP - 第6篇:图片滤镜效果实现
  • kotlin的gradle工程代码组织
  • 国内AI镜像站实测:GPT、Gemini、Claude三款旗舰模型技术比拼
  • HarmonyOS菜谱——分类页面的筛选逻辑与路由传参
  • Claude Code与MCP协议实战:从配置到项目集成的AI编程助手进阶指南
  • Android-基础-RTC、NTP​ 和 System Time(系统时间)
  • 鸿蒙打包、签名、加固、上架全流程实战(商用 APP 上线终极教程)
  • OpenGL 离屏多重采样抗锯齿 (Off-screen MSAA)
  • GitHub Copilot概述