ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Docker本地服务器搭建与免费公网映射实操指南

2026/10/8 2:50:11 拓冰建站 浏览量
Docker本地服务器搭建与免费公网映射实操指南 前阵子有朋友找我帮忙说他本地用代码写了个小工具网站想让几个同学在外网也能访问但他不想为一个临时项目去租云服务器。我给的建议很简单本地搭建项目运行服务器用 Docker 把运行环境和依赖全部容器化再通过免费公网地址映射把服务暴露出去。整个过程不到一小时就跑通了比在云服务器上折腾还快。这个组合解决的是很多个人开发者和小团队的真实痛点没有公网 IP、不想付费买机器、又需要一个能被外网访问的运行环境。这篇文章把我这段时间用 Docker 搭建本地服务器、再映射到公网的完整实操记录整理出来覆盖环境安装、镜像加速、数据库容器化、三种免费公网映射方案的选型对比以及我在排障过程中踩过的坑。适合手里有项目但暂时没有服务器的你也适合刚接触 Docker、准备把服务真正跑起来的人。1. 为什么这套组合能成立本地服务器的定位与边界1.1 什么场景真的适合本地跑服务 公网映射先说结论这套方案不是用来替代云服务器的它是用来覆盖临时、低流量、快速演示这类需求的。我在实践中整理过几种典型场景个人开发环境本地写的接口服务、爬虫任务、数据处理脚本想让手机或者远程电脑随时访问不用每次开会都当着别人面敲 localhost。学习 Linux 和容器很多入门教程会让你租一台服务器练手但如果你只是学 docker 命令、compose 编排、端口映射拿自己电脑练更划算坏了大不了重装。项目演示和作品集接私活或者求职时把项目拉到本地一键启动再给面试官一个公网地址体验比重发录屏好得多。异地临时协作同事远程需要连你一台内网机器上的服务隧道一开半小时解决问题。这种场景的共同点是并发不高、不需要 7x24 可用、数据敏感度不高、成本不想投入。本地服务器天然具备这些特性而云服务器反倒显得笨重。1.2 Docker 在这里解决了什么核心问题如果你只是临时跑一个 python 文件直接开个端口映射出去也能用。但一旦涉及数据库、redis、nginx、多个服务之间的依赖直接在宿主机上装就会出现在我电脑上好好的这种经典问题。Docker 的价值就是把环境固定成镜像换一台机器也能秒级重建。具体到这套组合里Docker 做了三件关键的事端口固定容器启动时映射的端口是可配置的公网映射工具只需要跟宿主机的一个固定端口绑定后面换服务也不会乱。隔离污染MySQL、Redis、Nginx 都装在容器里不会把系统目录搞乱。我甚至见过有人在自己电脑上装 MySQL 失败后系统环境变量被改得乱七八糟最后重装系统的例子。一键复现一份 docker-compose.yml 就描述了整个项目拓扑重启电脑之后一条命令拉起所有服务这个特性在本地服务器场景里特别重要因为本地机器关机重启的频率远高于云服务器。1.3 边界要认清别拿它硬扛生产环境用完记得认怂。本地服务器有几个硬伤断电断网就歇菜。家庭宽带的上行带宽有限一般只有几十 Mbps并发一上去就丢包。动态 IP 和运营商封锁端口的问题随时可能出现。设备性能受限于你的日常电脑。所以我给朋友的方案里也写清楚了如果项目是要放广告、接支付、给大量用户提供长期服务的别省这个钱老老实实买云服务器。本地 公网映射更适合验证想法、开发联调、临时对外演示。先认清边界后面踩坑的次数能少一半。2. Docker 环境安装与启动故障排查实录2.1 Windows 上安装 Docker Desktop 的注意事项在 Windows 上Docker Desktop 依赖 WSL2。安装流程网上到处都是我只说几个容易翻车的点。安装之前先确认三件事CPU 虚拟化已经在 BIOS 里开启这个可以在任务管理器性能 - CPU里看到虚拟化已启用。Windows 功能里勾选了适用于 Linux 的 Windows 子系统和虚拟机平台。执行wsl --status能正常输出WSL 内核版本不能太旧。Windows 11 其实已经内置了大部分 WSL2 组件但如果你是从 Windows 10 升级上来的可能需要手动执行一次wsl --install装完 Docker Desktop 第一次启动可能会卡在引擎启动界面最常见的原因不是软件问题而是前置条件没满足。这时候先不要反复卸载重装去看日志Windows 下日志在%LOCALAPPDATA%\Docker\log\目录里面会直接告诉你卡在哪一步。2.2 virtualisation support not detected 的完整排查链路Docker Desktop 在 Windows 上最著名的一个报错是Docker Desktop failed to start because virtualisation support wasnt detected这句话的意思不是Docker 有问题而是你的系统当前没有提供虚拟化能力。我在排查这个报错时按下面顺序走了三轮才定位到具体原因你也可以照着查第一轮检查 BIOS。重启进 BIOS 设置找到 Intel Virtualization TechnologyIntel 平台或者 SVM ModeAMD 平台确保状态是 Enabled。很多品牌机出厂默认是关闭的这个坑非常隐蔽。第二轮检查 Windows 功能。控制面板 - 程序 - 启用或关闭 Windows 功能确认虚拟机平台和适用于 Linux 的 Windows 子系统勾选状态。勾选后需要重启而且注意只能重启不能只注销账户。第三轮用命令确认系统状态systeminfo输出里有一项Hyper-V 要求如果显示检测到虚拟机监控程序将不显示 Hyper-V 所需的功能说明你已经有了虚拟化能力问题可能出在 WSL 版本或者 Docker Desktop 配置上。这时候检查wsl --set-default-version 2把默认版本切到 2然后 在 Docker Desktop 设置里的 Resources - WSL Integration 里启用你要用的发行版。这个报错还有一种情况是电脑本身太老CPU 不支持 SLAT 或者没有 AMD-V/VT-x那就真的没办法换机器或者改用 Linux 系统跑 Docker Engine 吧。排查要按链路走不要看到报错就删掉重装浪费时间。2.3 Linux 下的安装与 permission denied 权限坑Linux 上装 Docker Engine 最简单的方式是用官方脚本curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh脚本装完你把当前用户加入 docker 组否则每条命令都要 sudo非常难受sudo usermod -aG docker $USER改完一定要重新登录一次或者执行newgrp docker让用户组变更生效。这里有个高频报错你很可能遇到permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock第一反应不要怀疑 docker 服务坏了先排查用户组。执行id看自己是否属于 docker 组。如果不在就是 usermod 之后没有重新登录导致组信息没刷新。如果在组里仍然报错再检查 docker 服务systemctl status docker sudo systemctl restart docker还有一种情况是 docker 服务起来之后立即崩掉常见原因是 daemon.json 配置了不可达的 registry-mirrors 或者 graph 路径无权限。这种时候看服务日志journalctl -u docker -n 50日志里会直接显示 daemon 启动失败的具体原因。2.4 镜像拉不下来不是网络不行是镜像源没配好很多新手在安装完 Docker 后第一件事就是跑docker run hello-world然后看到Unable to find image hello-world:latest locallydocker: Error response from daemon: pull access denied for hello-world这个报错出口在于后面的网络请求。docker pull 默认访问 Docker Hub 官方仓库在国内网络环境下经常超时。解决办法不是去找乱七八糟的代理而是配置镜像加速器。Linux 下编辑/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io ] }Windows 下在 Docker Desktop 的 Settings - Docker Engine 里编辑 JSON格式一样。改完重启 Docker再用docker info看 Registry Mirrors 是否生效。这里提醒一句加速器提供的节点质量会变化多准备几个备选源。我自己一般配置两个一个不行就换另一个。配置好后之前拉不下来的 hello-world、mysql、nginx 镜像基本都能秒级下载。3. 从零用 Docker 部署一个可访问的本地项目3.1 第一个容器hello-world 并不简单一旦镜像源通了docker run hello-world会打印一段欢迎信息很多人看一眼就过去了。但我建议你多理解一下这条命令背后发生了什么Docker 客户端去拉取 hello-world 镜像。镜像存在本地后daemon 创建容器。容器启动执行入口程序输出信息后立即退出。所以docker run hello-world其实不是一个常驻服务它验证的是 Docker 引擎的链路是否畅通。真正要跑服务我们需要让容器常驻。最简单的是先跑一个 nginxdocker run -d --name web-test -p 8080:80 nginx-d表示后台运行-p 8080:80把宿主机的 8080 端口映射到容器的 80 端口。跑完后浏览器访问http://localhost:8080看到 Welcome to nginx 页面你的本地服务器就算正式跑起来了。3.2 MySQL 容器装得起来更要连得上我在本地项目里最常用的是 MySQL 8.0。装容器本身不难难的是部署后客户端连不上。这里记录一次完整的部署方式。docker run -d \ --name mysql-demo \ -e MYSQL_ROOT_PASSWORDyourpassword \ -e MYSQL_DATABASEmyapp \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ mysql:8.0参数解释一下MYSQL_ROOT_PASSWORD是 root 密码MYSQL_DATABASE会在首次初始化时自动建库-v mysql-data:/var/lib/mysql是数据卷这是最关键的一步容器删了数据还在否则一个 rm 就回到解放前。部署完连不上的情况我排查顺序如下用docker ps确认容器处于 Up 状态不是 Exited。如果 Exited用docker logs mysql-demo看错误日志常见的是当前容器名已存在或者端口被占用。宿主机上执行docker exec -it mysql-demo mysql -uroot -p能进入容器内部的话说明容器本身是好的问题大概率出在外部连接配置。宿主机 3306 端口被系统防火墙或者安全软件拦截Windows 上可能是网络配置文件类型的问题Linux 上检查ufw status。还有一个经典的 MySQL 8.0 坑默认认证插件是caching_sha2_password老版本的客户端、连接工具不一定支持。如果你用本地 GUI 工具连不上但命令行能连多半是这个原因。可以在容器内执行一次 SQL 把 root 的认证方式改回mysql_native_password或者直接用新版的连接驱动。3.3 用 docker compose 编排整个项目单容器只是热身。大多数项目至少有前端、后端、数据库三个环节这时候我推荐直接用 docker compose别再用一堆 docker run 手动拼装。写一个最小可运行的docker-compose.yml模拟前后端加数据库的完整结构version: 3.8 services: db: image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: myapp volumes: - mysql-data:/var/lib/mysql ports: - 3306:3306 backend: build: ./backend restart: always depends_on: - db ports: - 8000:8000 environment: DB_HOST: db DB_PASSWORD: rootpass web: image: nginx:alpine restart: always depends_on: - backend ports: - 8080:80 volumes: - ./frontend:/usr/share/nginx/html volumes: mysql-data:这个文件里最值得留意的几个设计depends_on只控制启动顺序不保证服务真的就绪。如果后端在数据库还没完全初始化时就连接会重试失败。要保险一点可以在后端代码里加数据库连接重试机制或者用一个 entrypoint 脚本先 ping 3306 端口。容器之间通信使用服务名比如后端配置DB_HOST: dbDocker 会把这个名字解析到 db 容器的内网 IP。不要在后端里写localhost那指向的是后端容器自己。restart: always让容器在电脑重启后自动拉起这对本地服务器的体验提升非常大。启动命令就三条docker compose up -d docker compose ps docker compose logs -f backend以后项目换到新电脑把整个目录拷贝过去docker compose up -d一把梭所有服务都能恢复。这就是环境即代码的好处。3.4 容器网络的坑网络不通先别甩锅给代码容器化项目最容易让人抓狂的问题是网络不通。现象千奇百怪后端连不上数据库、网页加载超时、API 请求失败。遇到这种问题我建议按下面的顺序排查不要一上来就改代码第一确认容器间的网络关系。默认情况下 compose 会创建一个 bridge 网络所有 service 都在同一个网络里通过服务名互相访问。如果你用多个独立的docker run启动容器它们默认不属于同一个网络就需要额外连接docker network create app-net docker run -d --network app-net --name mysql-demo ... docker run -d --network app-net --name backend ...第二确认端口映射是否正确。宿主机访问容器服务用localhost:映射端口容器之间访问用服务名:容器内端口。两个概念不要混。之前有人问我为什么 baidu 能访问但我的 nginx 暴露不了我一查他映射的是容器内 8080 端口但 nginx 缺省只监听 80这不就是映射错位嘛。第三检测网络连通性。进入容器内部 ping 一下docker exec -it backend sh ping db如果 ping 不通但服务名解析正确检查容器是不是处在不同的 network 里。如果解析都失败检查自定义网络配置。第四别忽略宿主机防火墙。很多本地服务器项目部署成功了自己访问 localhost 没问题手机却访问不了局域网 IP多半是宿主机防火墙拦截了对应端口。Windows 会弹窗问你是否允许 Docker 访问网络别手滑点了取消。4. 免费公网地址映射三种方案选型与实操4.1 内网穿透到底做了什么本地服务器跑通之后最大的问题来了你的电脑在 NAT 后面没有公网 IP外网用户怎么访问打个比方你家在小区里别人要寄快递给你只知道你的姓名没有用需要门牌号。公网 IP 就是门牌号。而内网穿透工具做的事情相当于你在小区门口开了一个收发室有一个公共的地址负责接收信件再把信件转交给你。用户的请求先到达这个公共地址再通过隧道转发到你本地的 nginx 端口上整个过程就是反向代理加隧道。免费公网地址映射的方案我实际用下来比较推荐三个方向Cloudflare Tunnel、frp、ngrok。下面逐个讲操作方法和适用场景。4.2 方案一Cloudflare Tunnel我目前最推荐的免费方案Cloudflare 官方提供了一条免费隧道可以把本地服务直接暴露到公网。它最大的优势是不需要自己的公网 IP不需要在路由器上做端口映射还能自动带 HTTPS 证书。最简单的用法甚至不需要注册账号。安装 cloudflared 后执行一行命令cloudflared tunnel --url http://localhost:8080运行后终端里会出现一个https://xxx.trycloudflare.com的随机地址把它发给访问者就能直接打开你本地 8080 端口上的服务。这个模式叫 Quick Tunnel适合临时演示每次启动地址都会变。如果你希望地址固定并且用自己的域名那就走正式流程cloudflared tunnel login cloudflared tunnel create my-tunnel cloudflared tunnel route dns my-tunnel demo.example.com cloudflared tunnel run my-tunnel这几条命令做的事情分别是授权 Cloudflare 账号、创建一条隧道、把你域名的一个子域名解析到这条隧道、启动隧道。启动成功后访问https://demo.example.com就会通过 Cloudflare 的边缘网络转发到本地 8080 端口。这个方案有个现实问题Cloudflare 的边缘节点多数在海外国内访问速度要看运气有时候快有时候慢。如果你面向的用户在国内可能需要配合一个经过速度测试的备用方案。但就免费、稳定、HTTPS 自动搞定这三点而言Cloudflare Tunnel 非常能打。4.3 方案二frp 自建隧道把控制权握在自己手里frp 是一个成熟的内网穿透工具分为服务端和客户端两部分服务端 frps 运行在一台有公网 IP 的机器上客户端 frpc 运行在你本地电脑上。它的思路就是利用一台公网服务器做中转把本地服务映射到服务器的某个端口上。这个方案有个前提条件你要有一台有公网 IP 的机器云服务器也可以。你可能会问既然有云服务器为什么不直接把服务部署上去因为有些场景比如本地有大型数据文件、硬件设备数据、内部系统的临时接口把服务留在本地只把访问通道开放出去确实更合适。frps 的配置frps.tomlbindPort 7000启动服务端./frps -c frps.toml客户端配置frpc.tomlserverAddr 你的公网服务器IP serverPort 7000 [[proxies]] name web type http localPort 8080 customDomains [frp.example.com]启动客户端./frpc -c frpc.toml如果 service 类型用http还需要在公网服务器上用 nginx 把frp.example.com反代到 frps 的 vhost 端口默认 80。如果只是临时测试也可以直接用type tcp加remotePort 8080这样访问公网IP:8080就直接打到你本地 8080。frp 的优点是数据路径可控、不依赖第三方平台、服务端在你自己手里速度取决于你公网服务器的带宽。缺点是你要维护一台公网中间机并且为了安全要单独配置 token 做鉴权。有动手能力的朋友首选这套它能玩出的花样最多。4.4 方案三ngrok适合 5 分钟内完成临时分享ngrok 是老牌内网穿透工具注册账号后下载客户端配置 authtoken然后执行ngrok http 8080它会给一个临时公网地址比如https://abc123.ngrok.io马上就能访问。ngrok 的免费版限制也比较明显域名随机、每月流量有限、连接数有上限速度一般。对临时把接口甩给前端同学调试来说足够了但不适合长期稳定的公网映射。4.5 三种方案对比折腾程度和稳定性成正比方案成本固定域名HTTPS部署难度稳定性适用场景Cloudflare Tunnel 快速版免费每次随机自动极低中临时演示、朋友访问Cloudflare Tunnel 正式版免费 自己的域名支持自动低较高长期个人项目frp 自建需要一台公网服务器支持自己配证书中取决于服务器可控性要求高的场景ngrok 免费版免费随机自动极低一般快速分享源码、调试回调简单说要省事选 Cloudflare要可控选 frp只是图个临时体验选 ngrok。我个人长期使用的是 Cloudflare 正式隧道 一个域名因为它维护成本几乎为零。5. 暴露公网之前的安全加固与运行调优5.1 我每次暴露前必做的安全检查清单公网地址一发出去你的本地服务就不再只属于你了。攻击者、扫描器、恶意爬虫随时可能出现。所以我在把服务映射出去之前会强制自己过一遍下面的清单最小化端口暴露只映射必需端口。比如后端 API 就只映射 8000不要顺手把 3306、6379 也映射出去。数据库、Redis 这些内部组件仅允许容器间通信不暴露到宿主机 0.0.0.0。容器权限收窄不要用 root 启动业务容器。例如内存占用较大的场景用--user指定普通用户运行。密码和密钥数据库别再用初始密码账号权限最小化。给服务加个最简单的 Basic Auth或者配置 Token 校验。防火墙兜底在宿主机上配置防火墙规则只放行需要对外开放的端口其余一律拒绝。数据卷备份本地磁盘虽然不贵但数据无价。建议把 MySQL 数据目录在系统层面做定时备份或者用容器卷复制到移动硬盘。这些动作不会花太多时间但能极大地降低出事的概率。去年有个开源项目因为开发者在 frpc 配置里把整个内网网段映射到了公网结果一顿操作被人扫到内网设备直接暴露在公网上教训相当深刻。5.2 HTTPS 和域名免费方案也能做到很体面HTTPS 不只是为了锁图标更是防止数据在中间链路被窃取。不同方案处理方式不一样Cloudflare Tunnel自动给你分配有效证书正式隧道用自己域名时也自动签 HTTPS完全不用操心。frp 公网服务器可以用 Lets Encrypt 给域名签免费证书再由服务器上的 nginx 终止 TLS再把明文流量通过 frp 内网转发。这样就算 frp 这一层没有 TLS外部链路也是加密的。ngrok官方域名自带证书免费可用。这里我要强调一个坑如果你在 HTTP 和 HTTPS 之间切换浏览器有 HSTS 缓存可能导致强制跳转。本地测试时我习惯统一走 HTTPS避免后续访问被浏览器误伤。5.3 长期稳定运行的调优经验本地服务器在公网映射下要长期可用有几个细节值得做一是容器加上restart: always。这个配置我在前一章已经提过但还是要重复一次因为它直接决定了断电重启后还要不要手动去敲命令。二是映射进程开机自启。cloudflared 或者 frpc 这类隧道进程必须跟着系统启动。Cloudflare 的 cloudflared 可以注册成系统服务sudo cloudflared service installfrpc 则可以用 systemd 或者 Linux 下的 supervisor 托管。三是限制容器资源。本地电脑不能因为一个项目就把内存吃干。在 compose 文件里加资源限制deploy: resources: limits: cpus: 0.5 memory: 512M四是日志清理。容器长时间运行Json File 日志会越积越大。在 daemon.json 里配置日志轮转{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这些配置加起来本地服务器就能像一台小型的生产环境一样稳定运行平时几乎不需要介入。6. 高频错误速查表与我的经验总结6.1 我遇到过的几次典型排障速查表写到最后把最近实战中遇到的几个错误现象和对应处理方式整理成一张表方便你直接对照错误 / 现象可能的根因我的处理方式Docker Desktop failed to start because virtualisation support wasnt detectedBIOS 虚拟化关闭 / WSL 未启用 / Hyper-V 缺失按 2.2 节的链路逐层检查先 BIOS 再 Windows 功能最后 WSLpermission denied while trying to connect to the Docker daemon socket当前用户不在 docker 用户组或者重新登录未生效sudo usermod -aG docker $USER后重新登录必要时执行newgrp dockerUnable to find image hello-world:latest locallydocker pull 超时镜像加速源未配置或源节点失效在 daemon.json 配置至少两个可用的 registry-mirrors改完重启 dockerMySQL 容器起来了外部连接超时端口映射错误、宿主机防火墙拦截、认证插件不兼容先 docker exec 进容器确认服务正常再检查宿主机防火墙最后检查客户端认证方式docker compose启动服务后访问不到页面端口映射写错、容器内服务监听地址不是 0.0.0.0检查 compose 里的 ports 映射方向确认容器内进程监听的 IP 和端口公网地址能访问但速度很慢隧道中转节点问题 / 本地上行带宽瓶颈换一条隧道线路或者用 frp 选择距离近的公网服务器中转6.2 踩过几次坑之后的实际操作习惯上面这些坑我都踩过吃过亏之后我固定下来一套操作习惯你可以直接抄作业第一每搭建一个项目配置文件先固定版本。docker-compose.yml、daemon.json、cloudflared 的配置都放进项目的.infra目录里用 git 管理起来。不要用临时文件的心态去写。第二公网映射开通之后第一时间用手机 4G 网络访问一遍。很多人测试只用本地浏览器中间多了个代理本地访问正常不代表公网访问正常。我每次都是切换到不含 Wi-Fi 的手机流量测能通才算真的通。第三数据库和敏感服务不暴露公网。哪怕你只是想远程连一下数据库也不要直接把 3306 映射出去。更稳妥的做法是只映射 Web 服务数据库连接通过后端接口间接完成。第四记录每次操作的完整命令。我习惯把项目 README 里写清楚启动服务的命令、更新数据库备份的方式、隧道重启的命令。本地服务器不像云服务器有完整的运维体系你只能靠笔记和 README 来保障。6.3 这套组合后续可以往哪扩展这套本地 Docker 公网隧道的玩法本质上是一个可复用的基础设施模板。我后来在它上面扩展过不少东西把自建的 GitLab 跑在本地用 Cloudflare 隧道给团队做远程仓库访问部署一个网盘类的应用 kodbox当作私人文件站备份一些家庭照片和文档拿同一套 compose 结构去跑不同的独立项目换一个 services 段就能交付下一个项目。技术上还能往上加的东西很多端口转发、微服务网关、Prometheus 监控、基于 Docker 的持续部署流水线。不过这些都是后话最重要的是先把本地服务器搭起来、把公网地址打通、把 Docker 这套工作流吃透。基础牢固了后面的扩展都是水到渠成的事。最后再分享一个小习惯。我在项目根目录放了一份 README里面写清了完整的启动命令、隧道映射命令、数据库密码和数据卷备份方式。每次重启环境或者换电脑照着这份文档一条条执行就能恢复。公网映射这种东西永远要在能用的时候先把流程固化下来别等服务挂了才翻聊天记录找命令。