☰
Docker部署DbGate:数据库Web管理、内网穿透与安全加固
2026/10/27 4:33:14 网站建设 项目流程

数据库管理这件事,麻烦的从来不是写 SQL,而是"我人不在那台机器前面"。库放在公司内网的测试服务器上、放在家里那台常年开着的迷你主机上、或者放在某个机房的物理机里,人在外面出差,临时要改一条数据、查一次表结构、导一份 CSV,就得找同事开远程、发截图、来回确认字段。装个桌面客户端也不行,因为很多人的工作电脑是临时借的,装软件要审批,装完还得配连接串。

这套需求的解法其实很清晰:把数据库管理界面做成一个网页服务,跑在 Linux 主机上,用 Docker 一条命令拉起来,再想办法让外部浏览器能访问到这个网页。DbGate 就是这类工具里比较顺手的一个,开源、支持 MySQL、PostgreSQL、SQL Server、MongoDB、SQLite、Oracle 等一堆引擎,界面是浏览器里跑的,不用在每台机器上装客户端。把内网服务映射到公网访问,也就是大家常说的内网穿透那类需求,配好之后你在任何一台能上网的电脑上打开浏览器就能干活。

下面这篇内容,我按自己的实际操作顺序来写:先讲清楚为什么选 DbGate 和容器化,再讲部署时哪些参数不能拍脑袋定,然后把公网接入的几种做法摊开对比,最后是安全加固和踩过的坑。适合手里有一台 Linux 主机、想给自己或者小团队搭一套数据库管理入口的人看,新手照着做能跑通,有基础的可以直接跳到第 3、4 节抄配置。

1. 先把场景捋顺:为什么是 DbGate 加容器

动手之前把"为什么这么选"想明白,比照着命令敲一遍更值钱。因为后面每一个参数、每一次取舍,都是从这个场景里推出来的。我见过太多人把服务跑起来了,结果发现数据没挂载、容器一重建配置全丢,又得从头来一遍。

1.1 DbGate 到底补了哪块短板

DbGate 的定位是"浏览器里的数据库管理工具",功能上对标那些装机必备的桌面客户端:SQL 编辑器带语法高亮和补全、表数据可以像 Excel 一样直接改、支持导入导出 CSV 和 JSON、能看 ER 图、有查询历史、还能对比两个库的表结构差异。它的价值不在于功能比桌面软件全,而在于入口集中。

举几个我实际遇到的场景。第一,手上有五六台机器,每台都装客户端,版本还不一致,某个机器连 PostgreSQL 15 报驱动错误,排查半天是客户端太老。换成网页版,服务端升级一次,所有访问的人同步受益。第二,临时用别人的电脑处理线上问题,装软件不现实,浏览器打开就能用。第三,团队里非专职的同事只是偶尔要看一眼数据,给他一个只读连接,比让他学客户端配置省事得多。

DbGate 还有个我比较喜欢的点:它自己用 SQLite 存配置和查询历史,整个数据目录就是一个文件夹,备份的时候直接打包拷走,迁移到新机器也就是把这个目录挂上去,比那些把配置散落在注册表和用户目录各处的客户端省心。

1.2 为什么用容器跑,而不是源码编译或者系统包

DbGate 是 Node 技术栈的应用,源码跑起来要处理运行时版本、依赖树、构建产物,升级的时候还得重新拉代码重新构建。系统包的方式在部分发行版里有,但版本往往落后,出了问题也不好定位。容器的好处是把"运行时 + 依赖 + 应用"打包成一个不可变的东西,你只要操心两件事:端口映射和数据目录挂载。

容器化真正的威力在升级环节。从旧版本换到新版本,正常流程是docker compose pull然后docker compose up -d,几十秒完成,出问题回滚就是把 compose 文件里的 tag 改回旧版本再 up 一次。如果是源码部署,回滚意味着重新 checkout、重新构建,中间还可能出现依赖不兼容。

当然容器也有代价,得说清楚。第一,镜像体积不小,初次拉取看网络情况,慢的时候要等几分钟。第二,容器里的用户 ID 和宿主机不一定对得上,挂载目录权限没处理好会报写入失败,这是新手最容易卡住的地方。第三,容器是无状态的,所有需要留下来的东西必须显式挂出来,忘了挂就等于给自己埋雷。

1.3 整条链路长什么样

我习惯把这条链路拆成三层来想,出问题的时候也按这三层排查,效率高很多。

内网层:Linux 主机上跑着 DbGate 容器,它监听本机的 3000 端口;同一台主机或者内网其他机器上跑着要管理的数据库,比如 MySQL 的 3306。这两者之间是内网通信,不需要任何额外配置。

出口层:把内网的某个端口映射到一台有公网地址的机器上。这里有两种方向,一种是把 DbGate 的 3000 端口暴露出去,浏览器直接访问管理界面;另一种是把数据库端口暴露出去,DbGate 部署在公网机器上去连内网库。我推荐前者,因为 DbGate 本身有登录口令、有操作日志,比直接暴露数据库端口安全得多。

接入层:浏览器访问公网地址,中间最好套一层 HTTPS,账号口令别明文在公网上裸奔。

这三层里,最容易出问题的是出口层,因为它涉及两台机器的配置配合;最容易被忽视的是接入层,很多人跑通就开始用,安全配置一直欠着。

2. 部署前的功课:环境、端口与目录规划

我见过太多"命令跑完了但用不了"的情况,八成是这一步没做。花十分钟把环境摸清楚,后面能省两小时。

2.1 主机与 Docker 环境自查

先确认内核和 Docker 版本。DbGate 镜像本身对内核要求不高,但如果你后面要用到端口转发、连接跟踪这些能力,较新的内核会省掉很多莫名其妙的报错。

uname -r cat /etc/os-release | head -3 docker version --format '{{.Server.Version}}' docker compose version df -h /opt free -h

看几个点。内核版本在 4.x 以上比较稳妥,3.10 也能跑但某些网络特性会缺。Docker 服务端版本建议 20.10 以上,因为 compose v2 插件依赖这个量级。磁盘空间方面,DbGate 自身数据目录很小,几十兆的量级,但你要是经常导入导出大表,临时文件会占空间,我一般给/opt留至少 10G 余量。内存 1G 起步够用,2G 更从容。

如果docker compose version报错说找不到命令,说明装的是老的docker-compose独立二进制,或者插件没装。这种时候别硬凑,按发行版的包管理方式把 compose 插件补上,后面所有命令都统一用docker compose(中间是空格),别混着写。

2.2 端口规划,先画表再动手

端口冲突是部署阶段最高频的问题。我习惯在动手前把要用的端口列成一张表,写清楚每个端口监听在哪个地址、给谁用、能不能对外。

端口用途建议绑定地址是否对外说明
3000DbGate Web 界面127.0.0.1否只给本机和转发通道用
3306MySQL 服务127.0.0.1 或内网地址否数据库不直接对外
5432PostgreSQL 服务127.0.0.1 或内网地址否同上
13306转发出口(数据库方向)公网主机地址是仅放行必要来源
18080转发出口(界面方向)公网主机地址是建议前面套 HTTPS

这张表里最关键的一行是 3000 端口绑定到127.0.0.1。很多教程直接写-p 3000:3000,等价于监听0.0.0.0,意思是你内网里任何一台机器扫到这个端口都能打开管理界面。在家里的小网络也许无所谓,在公司内网就是明显的风险。绑到回环地址之后,只有本机和转发通道能访问,攻击面立刻小一圈。

注意:如果你确实需要内网其他同事直接访问,也别绑0.0.0.0完事,至少用防火墙限制来源网段,同时把 DbGate 的登录口令开起来。

2.3 数据目录和权限,别等报错再回头

DbGate 容器内的运行用户是dbgate,UID 通常是 1000。挂载到宿主机的目录如果不给写权限,表现是界面能打开,但登录后一操作就报错,或者配置保存不了。这种错误信息往往埋在容器日志里,不翻日志根本看不出来。

sudo mkdir -p /opt/dbgate/data sudo chown -R 1000:1000 /opt/dbgate sudo chmod 750 /opt/dbgate/data ls -ln /opt/dbgate

ls -ln用数字显示属主,比名字直观,能直接看出是不是 1000。这里别图省事直接chmod 777,数据目录里存着所有数据库连接的口令,权限放太开等于把钥匙插在门上。

如果宿主机上 1000 这个 UID 已经被别的账号占用,那就换个思路:不用改宿主机文件属主,改成在 compose 里指定user: "1000:1000"之外的方式,或者干脆新建一个专用账号把 UID 对齐。我实际用的做法是宿主机建一个dbgate用户,UID 设成 1000,目录归它所有,干净利落。

3. 用 Docker 拉起 DbGate:从跑起来到跑得舒服

到这一步环境已经清楚了,可以动手。我的习惯是先跑最小命令验证能通,再换成 compose 固化配置,最后补上预置连接这类提升体验的东西。一次到位写一大坨配置,出错了不好定位。

3.1 最小可用命令与参数逐条拆解

docker run -d \ --name dbgate \ --restart unless-stopped \ -p 127.0.0.1:3000:3000 \ -v /opt/dbgate/data:/home/dbgate/.dbgate \ -e LOGIN_PASSWORD='替换成一个足够长的口令' \ dbgate/dbgate:latest

逐条说清楚每个参数为什么这么写。

-d后台运行,不加这个前台跑着,一关终端服务就没了。--name dbgate给容器起名字,后面看日志、重启、进容器都用这个名字,比记那串随机 ID 舒服。--restart unless-stopped是重启策略,主机重启或者 Docker 服务重启后容器自动拉起,除非你手动stop过。这个参数很容易被忘,忘了的后果是某次主机重启后你以为服务还在,实际上已经停了。

-p 127.0.0.1:3000:3000三个部分分别是"宿主机监听地址:宿主机端口:容器端口"。容器里的 DbGate 固定监听 3000,宿主机这边我把地址限定在回环,端口也用 3000,保持一致好记。

-v /opt/dbgate/data:/home/dbgate/.dbgate是整条命令里最重要的一行。冒号左边是宿主机目录,右边是容器内 DbGate 的配置存储位置。冒号写反了、路径写错了,容器照样能启动,界面照样能打开,但所有配置都写在容器内部的可写层,容器一删全没。这个坑我踩过一次,第二天重建容器发现所有连接配置和查询历史清空,只能一个个重新添加。

-e LOGIN_PASSWORD=...用来开启 Web 端登录。不设置这个变量,界面是裸奔状态,谁打开都能操作。口令别用生日、别用admin123,长度至少 16 位,混合大小写数字符号。虽然这是内网服务,但你既然打算暴露到公网,就得按公网的威胁模型来配。

镜像 tag 我写的是latest,图省事。生产场景我建议钉到具体的版本号,比如dbgate/dbgate:5.4.0这种,避免某次pull之后版本跳变带来界面或配置格式变化。版本号从镜像仓库的 tag 列表里挑最新的一个填即可。

跑完之后验证:

docker ps --filter name=dbgate docker logs --tail 50 dbgate curl -I http://127.0.0.1:3000

curl返回 200 或者 302 就说明 Web 服务起来了。如果返回空、或者连接被拒绝,先看docker ps里状态是不是Up,再翻日志找报错。

3.2 换成 compose 管理,改配置不用背命令

命令能跑通之后,我会立刻转成 compose。原因很实在:参数一多,docker run那行命令就没法看了,改一个参数要回溯整行,改错了还得重新敲。compose 文件是声明式的,改完up -d就行,还能进版本管理。

services: dbgate: image: dbgate/dbgate:latest container_name: dbgate restart: unless-stopped ports: - "127.0.0.1:3000:3000" volumes: - ./data:/home/dbgate/.dbgate environment: LOGIN_PASSWORD: "替换成一个足够长的口令" TZ: "Asia/Shanghai" healthcheck: test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3000 || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 40s

放到/opt/dbgate/docker-compose.yml,然后cd /opt/dbgate && docker compose up -d。这里挂载写的是./data,相对路径基于 compose 文件所在目录,等价于/opt/dbgate/data,看着更清爽。

TZ这个环境变量我一般都会加上。不设的话容器内是 UTC,界面上查询历史的时间戳会差 8 小时,看着别扭,排查问题时还容易把自己绕进去。

健康检查那几行是可选的,但值得加。它让docker ps的 STATUS 栏显示healthy而不是干巴巴的Up,服务假死的时候一眼能看出来。start_period给足 40 秒,是因为 Node 应用冷启动需要时间,给太短会一直误判成不健康。

日常操作就三条:

docker compose logs -f --tail 100 # 看日志 docker compose restart # 重启 docker compose pull && docker compose up -d # 升级

升级前记得先备份数据目录,tar -czf dbgate-backup-$(date +%F).tar.gz ./data,几十兆的事,图个心安。万一新版本有问题,把 image 的 tag 改回旧版本再up -d,一分钟回滚。

3.3 预置数据库连接,少点几次鼠标

DbGate 支持通过环境变量预置连接,容器启动后界面上直接就有这些连接,不用一个个手动填。小规模场景手动加几个也无所谓,但如果你要管五六个库,或者要给同事发一套现成的环境,这个功能很省事。

规则是:先用CONNECTIONS列出连接名,然后用LABEL_、SERVER_、PORT_、USER_、PASSWORD_、ENGINE_、DATABASE_这些前缀加连接名来传参数。

environment: CONNECTIONS: "localmysql,localpg" LABEL_localmysql: "内网 MySQL 主库" SERVER_localmysql: "192.168.1.20" PORT_localmysql: "3306" USER_localmysql: "readonly_user" PASSWORD_localmysql: "只读账号的口令" DATABASE_localmysql: "app_db" ENGINE_localmysql: "mysql@dbgate-plugin-mysql" LABEL_localpg: "内网 PostgreSQL" SERVER_localpg: "192.168.1.21" PORT_localpg: "5432" USER_localpg: "analyst" PASSWORD_localpg: "口令" ENGINE_localpg: "postgres@dbgate-plugin-postgres"

ENGINE的取值是"引擎名@插件名"的组合,我实际用过的几个对应关系如下,第一次配的时候容易记错,直接抄表:

数据库类型ENGINE 取值
MySQL / MariaDBmysql@dbgate-plugin-mysql
PostgreSQLpostgres@dbgate-plugin-postgres
SQL Servermssql@dbgate-plugin-mssql
MongoDBmongo@dbgate-plugin-mongo
SQLitesqlite@dbgate-plugin-sqlite
Oracleoracle@dbgate-plugin-oracle

这里有个必须提醒的点:写进环境变量的口令不是秘密。任何能在这台机器上执行docker inspect dbgate的人都能看到明文。所以我的用法是,环境变量里只放测试库、只读账号这类低敏感连接,生产库的口令一律在界面里手动添加,不落进配置文件。

另外,CONNECTIONS里列出的连接,删掉某个环境变量后重启容器就会消失,别把通过环境变量预置的连接当成长期存储,重要配置还是在界面里改,让它写进 SQLite 数据文件。

4. 打通公网访问链路:三种可落地的做法

前面都是单机上的事,真正有技术含量的是这一段。目标很简单:让外部浏览器能访问内网的 DbGate。实现路径有好几条,各有各的适用条件,我把三种都讲一遍,你按手上的资源选。

4.1 方案A:公网主机加 SSH 反向转发

这是我用得最多的一种,因为它不需要额外的软件,Linux 自带 SSH 就能干,而且通道是加密的。前提是你手上有一台有公网地址的 Linux 机器,配置不用高,最便宜的最低配都够。

先理解方向。SSH 的参数里,-L是把本地端口转到远端,-R是把远端端口转到本地。我们要的是"在公网机器上开一个端口,流量回推到内网的 3000 端口",所以用-R。

在内网那台跑着 DbGate 的机器上执行:

ssh -N -T -R 18080:127.0.0.1:3000 tunneluser@公网主机地址

-N表示不执行远程命令,-T表示不分配终端,两条合起来就是只建通道不干别的。-R 18080:127.0.0.1:3000的意思是:在公网主机上监听 18080,收到的流量转发到内网机器自己的 127.0.0.1:3000。通道建好之后,外部浏览器访问http://公网主机:18080就能看到界面。

这里有个必踩的坑:默认情况下,远端监听只绑在公网主机的回环地址上,也就是只有公网主机自己能访问,外部照样连不上。要放开,得改公网主机的/etc/ssh/sshd_config,加上:

GatewayPorts clientspecified

改完systemctl reload sshd。这个参数的意思是允许客户端自己指定监听地址,属于最小授权,比设成yes强制绑所有地址安全一些。改完之后,把命令里的远端端口写成0.0.0.0:18080更明确:

ssh -N -T -R 0.0.0.0:18080:127.0.0.1:3000 tunneluser@公网主机地址

手工敲命令的通道活不过一个断网或者一次睡眠。要让它在后台长期在线,我一般用autossh加 systemd 托管:

[Unit] Description=DbGate Tunnel After=network-online.target Wants=network-online.target [Service] User=dbgate Environment=AUTOSSH_GATETIME=0 ExecStart=/usr/bin/autossh -M 0 -N -T \ -o "ServerAliveInterval 30" \ -o "ServerAliveCountMax 3" \ -o "ExitOnForwardFailure yes" \ -o "StrictHostKeyChecking accept-new" \ -i /home/dbgate/.ssh/tunnel_key \ -R 0.0.0.0:18080:127.0.0.1:3000 \ tunneluser@公网主机地址 [Install] WantedBy=multi-user.target

ServerAliveInterval 30和ServerAliveCountMax 3让连接每 30 秒发一次心跳,连续三次没响应就判定断开并重建,这正是长连接的保命配置。ExitOnForwardFailure yes很关键,表示端口占用导致转发失败时直接退出,让 systemd 重试,而不是挂着一条无效通道假装健康。-M 0是关掉 autossh 自带的监控端口,依赖 SSH 层心跳就够了。

认证方式务必用密钥,ssh-keygen -t ed25519生成一对,公钥丢到公网主机的~/.ssh/authorized_keys,权限设成 600。同时在公网主机的 sshd 配置里关掉密码登录、关掉 root 直登。通道账号权限也别给大,建个普通账号,只用来转发。

4.2 方案B:NAT 端口映射,有公网地址时最省事

如果你手上有一台直接挂在公网上的机器,或者路由器能拿到固定公网地址,可以省掉 SSH 那一层,直接在网络边界做端口映射。这种方式性能最好,没有中间环节,但安全上要自己扛。

路由器那边的做法是在"端口映射"或者"虚拟服务器"页里,把外网端口 18080 映射到内网 DbGate 主机的 3000。各家界面叫法不同,本质都是 DNAT。如果是自己维护的 Linux 网关,命令大致是这样:

# 先开转发 sudo sysctl -w net.ipv4.ip_forward=1 echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/99-forward.conf # 把 18080 转到内网主机 3000 sudo iptables -t nat -A PREROUTING -p tcp --dport 18080 \ -j DNAT --to-destination 192.168.1.20:3000 sudo iptables -t nat -A POSTROUTING -d 192.168.1.20 -p tcp --dport 3000 \ -j MASQUERADE

第一条PREROUTING规则改的是"目标地址",把原本发往网关自己的包改写到内网主机;第二条POSTROUTING做源地址伪装,让内网主机的回应能正确回到发起方。两条缺一不可,只写第一条的表现是请求能到,但回不来,浏览器一直转圈。

iptables 规则重启即失效。要持久化,发行版之间差异挺大,Debian 系用iptables-persistent,RedHat 系用iptables-services,或者干脆在 nftables 里写规则集。不想折腾这些,就用ufw或者firewalld的端口转发能力,本质一样,只是包了层好用的壳。

这条路有个硬条件:你得有公网地址。很多家庭宽带的地址是运营商侧共享的,路由器 WAN 口上显示的地址和实际出口地址不一致,这种情况下端口映射配了也没用。判断方法很简单,在机器上curl ifconfig.me拿到出口地址,和路由器 WAN 口地址比一比,不一致就是没有独立的公网入口。

4.3 方案C:第三方接入通道,没有公网地址时的备选

既没有公网机器,也没有独立地址,还想从外面访问,就得靠"客户端主动往外连"的思路。原理不复杂:内网跑一个小客户端,它主动连接服务商的中转节点,建立一条持久的长连接;外部访问请求先到中转节点,再顺着这条已经建立的连接回推到内网服务。因为连接是从内往外发起的,所以不要求内网有任何对外可访问的入口。

这类服务商挺多,选型的时候我主要看几个维度。是否需要装客户端,有些只给你一个域名和端口,本地装个程序就完事,有些还要在手机端装 App 才能管配置。有没有带宽限制,免费档常见限制在上行几百 K,传个 80M 的备份文件能等到怀疑人生,所以如果只是偶尔改数据无所谓,要频繁导表就得看付费档。日志审计做不做,流量毕竟过了别人的节点,敏感数据的场景要慎重。是否支持自定义域名和 HTTPS,这个直接影响后面套证书的难易度。

配置流程大同小异,都是先在服务商那边创建一条通道,选好协议(HTTP 或 TCP)、本地地址(127.0.0.1:3000)、要分配的域名,然后把生成的令牌填到本地客户端配置文件里,启动服务。启动之后外部访问那个分配的域名,流量就被推到内网 3000 端口了。

关于这类方案我的态度是:能用,但要清楚代价。数据经过第三方节点,意味着链路上多了一个你无法审计的环节。真要用,先把 DbGate 的登录口令开到最强,生产库的连接别配在里面,只放测试环境的库,公私分明。

4.4 三种方案横向对比

维度方案A SSH 反向转发方案B NAT 端口映射方案C 第三方通道
前置条件一台公网 Linux 主机独立公网地址无,只需能出网
传输加密SSH 通道自带无,需另套 HTTPS视服务商而定
部署复杂度中,要配 SSH 和 systemd低,改路由或几条规则低,装客户端填令牌
断线自愈靠 autossh 与心跳不涉及客户端自带重连
带宽上限取决于公网主机带宽取决于本地出口常见有档位限制
数据经过第三方否否是
适合场景有公网主机、在意数据不外流有独立公网地址、追求简单临时用、无公网资源

我自己的选择顺序是 A 优先,B 次之,C 兜底。A 和 B 的数据都在自己可控的链路上,A 还自带加密,代价是要维护一台公网机器和一套 systemd 服务;B 最省事但要求高,而且暴露面直接怼在公网上,安全配置一点都不能省。

5. 安全加固:把门锁上再出门

服务能访问了,先别急着用。公网暴露的东西,被人扫到的时间单位是小时级别。我做过一个很粗暴的测试,随便开个高端口放两天,日志里的扫描请求就没停过。下面这几件事,我建议在正式使用前全部做完。

5.1 账号口令与权限收敛

DbGate 的登录口令是第一道门。16 位以上,别用有意义的词,也别和数据库口令复用。设置之后所有访问都要先过登录页,同时它会记录登录会话。

第二道门是数据库账号的权限。给 DbGate 配的连接,能用只读账号就别用读写账号。MySQL 里建一个只给SELECT的账号,几行 SQL 的事:

CREATE USER 'ro_viewer'@'192.168.1.%' IDENTIFIED BY '一个独立的长口令'; GRANT SELECT ON app_db.* TO 'ro_viewer'@'192.168.1.%'; FLUSH PRIVILEGES;

这样即便登录口令泄露,攻击者拿到的最多是一个只能看的账号,改不了删不掉。要改数据的时候再切到另一个权限更高的连接,用完就断开,减少暴露时间。

第三道门是连接来源限制。ro_viewer的主机部分我写的是192.168.1.%,意思是只允许内网网段登录,外面的 IP 想直接连数据库连不上,必须经过 DbGate 这一层。这一条经常被忽略,很多人图省事写'%',等于给数据库开了个任意来源的大门。

5.2 传输层与访问控制

明文 HTTP 在公网上跑,登录口令就是裸奔。你可能会觉得"我这个地址没人知道",但扫描器不靠猜,它是全网段扫端口的。套一层 HTTPS 的成本很低,用 Nginx 或者 Caddy 在 DbGate 前面做 TLS 终结,把证书配好,反正是标准活。

我的做法是在公网主机上装 Caddy,配置两行就能自动申请和续期证书,把 18080 换成一个域名,走 443。配置要点就三个:把域名解析到公网主机、让前置服务转发到本机 3000、把各类请求头原样透传。具体配置文件各家写法不同,这里不贴了,搜索"TLS 终结 配置"能查到大量可直接用的模板。

防火墙层面,我一般只放行必要端口,其余全关:

sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 443/tcp sudo ufw enable sudo ufw status verbose

SSH 那 22 端口如果要收紧,可以限定来源 IP:sudo ufw allow from 家里的出口地址 to any port 22。但如果出口地址是动态的,这么配会把自己锁在外面,取舍一下,或者干脆把 SSH 只绑在内网地址上,通过其他方式跳进去。

还有两个常规动作。一是 SSH 关掉密码认证,只留密钥;二是把 SSH 的PermitRootLogin设成no。这两条能挡掉绝大多数自动化爆破。

5.3 日志巡检与定期备份

DbGate 容器日志里有访问记录和错误信息,docker compose logs --since 24h能看最近一天的。我习惯每周扫一眼,重点看有没有大量 401、有没有陌生 IP 反复请求登录页。发现异常就封 IP,ufw deny from 可疑地址一行搞定。

数据备份别忘了。DbGate 的所有配置,包括连接信息、查询历史、保存的 SQL 片段,都在/opt/dbgate/data里。我挂了个定时任务,每天凌晨打包一次,保留最近 7 天:

0 3 * * * cd /opt/dbgate && tar -czf /var/backups/dbgate-$(date +\%F).tar.gz data && find /var/backups -name 'dbgate-*.tar.gz' -mtime +7 -delete

这个备份不光防误删,还有个用处:换机器迁移的时候,装好新容器,把 data 目录解压进去,up -d,所有配置原样恢复,不用重新配一遍。

6. 常见问题与排查实录

下面这些是我和同事实际碰到过的,按发生频率排序。每一条都写了判断依据,不是简单罗列。

6.1 页面打不开,按链路从内往外查

判断顺序应该是:容器 → 宿主机 → 通道 → 公网。由近及远,每一步只验证一件事。

# 第一步:容器活着吗 docker ps --filter name=dbgate # 第二步:容器内部服务正常吗 docker exec dbgate wget -qO- http://127.0.0.1:3000 | head -5 # 第三步:宿主机上能访问吗 curl -I http://127.0.0.1:3000 # 第四步:公网主机上能访问吗(在公网机器上执行) curl -I http://127.0.0.1:18080 # 第五步:通道监听的地址对不对(在公网机器上执行) ss -lntp | grep 18080

第五步能筛掉一半问题。如果ss显示监听地址是127.0.0.1:18080而不是0.0.0.0:18080,那外部肯定连不上,回去检查GatewayPorts clientspecified有没有生效、命令里有没有写0.0.0.0:前缀。改完 sshd 配置记得 reload,光改文件不 reload 是不生效的。

如果ss里根本看不到 18080,说明转发根本没建起来,回去看 SSH 命令的报错。最常见的是远端端口被占用,ExitOnForwardFailure yes会直接退出,日志里会有remote port forwarding failed字样,换个端口就好。

6.2 界面能打开,但点了连接就转圈

这类问题九成出在 DbGate 到数据库这一段,跟公网链路无关。先确认数据库本身活着,在宿主机上直接连:

nc -zv 192.168.1.20 3306 mysql -h 192.168.1.20 -P 3306 -u readonly_user -p -e "select 1"

如果宿主机连不上,那 DbGate 更连不上,问题在数据库侧,可能是服务没起、监听地址只绑了 127.0.0.1、或者防火墙拦了。MySQL 的bind-address配成127.0.0.1时只接受本机连接,内网其他机器连不上,这个配置在my.cnf里,改完要重启服务。

如果宿主机能连、DbGate 连不上,大概率是容器网络问题。容器里的127.0.0.1是容器自己,不是宿主机。所以连接串里写127.0.0.1一定失败,要写宿主机的内网地址,或者用host.docker.internal(需要额外配置--add-host)。这个坑很隐蔽,因为在宿主机上测什么都正常,一进界面就报连接超时。

还有一种情况是字符集或者 SSL 协商失败,报错信息里通常有SSL或者collation字样。MySQL 8 默认要求 SSL,老版本的驱动可能不认,在连接的附加参数里加sslmode=disable之类的选项试试,具体写法看引擎文档。

6.3 容器重建后配置全没了

八成是挂载路径写错,或者用相对路径时工作目录不对。验证方法是进容器看一眼数据目录里有没有文件:

docker exec dbgate ls -la /home/dbgate/.dbgate docker inspect dbgate --format '{{json .Mounts}}' | tr ',' '\n'

第二条能直接看到容器内路径和宿主机路径的对应关系。如果Mounts是空数组,说明根本没挂上,运行命令时-v就没生效。如果挂上了但路径对不上,比如容器内写成了/root/.dbgate,那数据还是写在容器层。

还有一种情况是挂载成功但没写权限,表现是界面能打开、能登录,但一保存配置就报错。翻日志能看到EACCES或者permission denied。回去按 2.3 节的办法把宿主机目录属主改成 1000。

6.4 常见问题速查表

现象最可能原因快速验证处理
外部访问超时远端监听绑在回环ss -lntp看监听地址开 GatewayPorts,命令加 0.0.0.0
通道几分钟就断没有心跳保活看 SSH 日志有无断开记录加 ServerAliveInterval
界面正常,连库超时容器内用了 127.0.0.1用 IP 替换回环地址改宿主内网地址
保存配置报错挂载目录无写权限docker exec ls -la看属主chown 到 1000
容器重建配置丢失未挂载或挂错路径docker inspect .Mounts修正 -v 路径
登录页一直提示密码错环境变量含特殊字符看日志有无解析错误口令加引号或改简单字符
时间显示差 8 小时容器时区为 UTCdocker exec date加 TZ 环境变量
大文件导入中断通道或服务端限流看是否有长度或超时限制分批次导入或走内网

这张表可以打印出来贴显示器边上,前四条能覆盖大部分新手卡点。

7. 几个我自己的使用体会

配完之后我用了大半年,有几个感受值得单独说。

环境变量预置连接这个功能,看着方便,但我后来基本不用了。原因是口令明文躺在 compose 文件里,而那台机器的备份、截图、分享配置的时候都可能带出去。现在我的做法是环境变量只配一个测试库,其他全在界面里加,加完 DbGate 会写进 SQLite,备份 data 目录就一起带走了,比散落在配置文件里干净。

SSH 通道的稳定性远比想象中重要。头一个月我用裸 SSH 命令跑,平均两天断一次,断了没察觉,等到用时才发现。换 autossh 加 systemd,再加心跳参数,之后基本没管过。这套配置花二十分钟搭一次,能省下后面无数次"怎么又连不上了"。

容器升级别贪快。新版本出来我一般等一两周,看看有没有人反馈问题再升。升之前先备份 data,升完登进去点几个连接试试,正常了再继续用。有一次升完发现某个引擎的插件加载失败,回滚回去十分钟解决,如果没有备份和旧 tag,那天下午就别干别的了。

最后一条,把管理入口和数据库入口分开。我见过有人直接在内网主机上做 3306 的端口映射,图省事,结果数据库口令被暴力猜解,因为 MySQL 的账号锁定机制默认不生效。走 DbGate 这一层,至少有个 Web 登录挡在前面,还能看访问日志,追溯起来有依据。多绕这一下,省的是后面填坑的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询