帮助中心

服务器、网络和账户的分步教程。

Linux 服务器

小内存 VPS 运行 Docker:安装、日志与内存限制、防火墙陷阱及磁盘清理

小内存 VPS 运行 Docker:安装、日志与内存限制、防火墙陷阱及磁盘清理

只要从 Docker 官方源安装、限制容器日志、为每个容器设置内存上限、把端口发布在 127.0.0.1 并由反向代理对外(否则 Docker 会绕过 UFW 和 firewalld),再定期清理旧镜像,Docker 在 1 GB 内存的 VPS 上也能稳定运行。本文按步骤介绍在 Debian 12/13、Ubuntu 22.04/24.04 和 Rocky Linux / AlmaLinux 上的做法。

步骤 1:从官方源安装 Docker

Debian 和 Ubuntu 自带的 docker.io 软件包往往版本较旧,也没有 Compose 插件,请改用 Docker 官方源。在 Debian 和 Ubuntu 上以 root 身份执行:

# Debian 12/13 and Ubuntu 22.04/24.04 - remove distro packages that conflict
for p in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
  apt-get remove -y "$p" 2>/dev/null
done

apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
. /etc/os-release        # sets ID (debian/ubuntu) and VERSION_CODENAME
curl -fsSL "https://download.docker.com/linux/$ID/gpg" -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

cat > /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/$ID
Suites: $VERSION_CODENAME
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

部分 Rocky Linux 和 AlmaLinux 镜像预装了 Podman,会与 Docker 的软件包冲突,请先卸载:

# Rocky Linux / AlmaLinux 8, 9, 10
dnf remove -y podman buildah runc 2>/dev/null
dnf install -y dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable --now docker

在 Debian 和 Ubuntu 上,服务会自动启动。检查一切是否正常:

systemctl is-active docker
docker version
docker compose version
docker run --rm hello-world
docker 组的成员可以控制 Docker 守护进程,这等同于拥有服务器的 root 权限。只用 usermod -aG docker username 添加可信用户,并且绝不要把 Docker API(/var/run/docker.sock 或 TCP 2375 端口)暴露到公网。

步骤 2:限制容器日志并开启 live-restore

默认情况下,Docker 会保存容器输出的每一行日志,且不限大小。一个日志量大的容器几天就能写满 20 GB 硬盘。请在 /etc/docker/daemon.json 中设置全局限制:

# /etc/docker/daemon.json
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "live-restore": true
}
  • local 日志驱动:压缩并轮转日志文件,这里每个容器最多 3 × 10 MB,docker logs 照常可用。
  • live-restore:Docker 守护进程重启或升级时,容器继续运行,软件包更新不会让网站中断。

如果服务器上有工具需要直接读取 JSON 日志文件,可改用 json-file,限制相同:

  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" },

校验配置文件并重启 Docker。这第一次重启会让正在运行的容器停止一次:

dockerd --validate --config-file /etc/docker/daemon.json
systemctl restart docker
docker info --format '{{.LoggingDriver}} live-restore={{.LiveRestoreEnabled}}'
日志设置只对之后新建的容器生效。请用 docker compose up -d --force-recreate 重新创建现有容器。

步骤 3:编写带内存和 CPU 限制的 compose 文件

不设限制时,一个内存泄漏的容器就可能让整台 VPS 疯狂使用 swap,或让内核杀掉 sshd 甚至数据库。设置上限后,只有这个容器会被杀掉并重启。在 1 GB 方案上运行一个小应用加 MariaDB,可以这样写:

# /opt/app/compose.yaml
services:
  app:
    image: ghcr.io/example/app:1.4.2      # pin a version, avoid :latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"             # reachable only from the VPS itself
    mem_limit: 256m
    memswap_limit: 256m                   # no extra swap for this container
    cpus: 1.0
    pids_limit: 200
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 30s
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mariadb:11.4
    restart: unless-stopped
    command: ["--innodb-buffer-pool-size=64M"]
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MARIADB_DATABASE: app
    volumes:
      - db_data:/var/lib/mysql
    mem_limit: 300m
    memswap_limit: 300m
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 60s

volumes:
  db_data:
  • mem_limit / memswap_limit:内存硬上限。两者设为相同值,容器就不会使用 swap。请给系统留出空间:1 GB 内存时,所有限制加起来控制在 600–700 MB 左右。
  • cpus:1.0 表示一个完整核心。这样即使某个容器很忙,也不会把整台 VPS 推过合理使用规则中的 CPU 平均值。
  • restart: unless-stopped:容器崩溃或服务器重启后会自动恢复,但你主动停止的容器不会被拉起。
  • healthcheck:把容器标记为 healthy 或 unhealthy,depends_on 会等待数据库就绪。检查命令(这里是 wget)必须存在于镜像中。注意:单纯的 Docker 不会重启 unhealthy 的容器,只会显示状态。

把密码等敏感信息放在 compose.yaml 旁边的 .env 文件中,然后启动:

cd /opt/app
echo 'DB_ROOT_PASSWORD=REPLACE_WITH_A_LONG_RANDOM_PASSWORD' > .env
chmod 600 .env
docker compose up -d
docker compose ps            # STATUS should become "healthy"
docker stats --no-stream     # memory use against each limit

步骤 4:避开防火墙绕过陷阱(UFW、firewalld 与 Docker)

这是使用 Docker 时最常见的安全错误。用 -p 8080:8080 或 "8080:8080" 发布端口时,Docker 会添加自己的 NAT 和转发规则。这些流量根本不经过 UFW 和 firewalld 管理的 INPUT 规则,所以即使 ufw status 显示已拦截,端口实际上仍对整个公网开放。

按推荐顺序,解决办法如下:

  1. 发布在 127.0.0.1 上("127.0.0.1:8080:8080",即上面 compose 文件的写法)。端口只能从 VPS 本机访问。
  2. 前面加一层反向代理。主机上的 Nginx 或 Caddy 监听 80/443(照常在 UFW 或 firewalld 中放行),再转发到 127.0.0.1:8080,HTTPS 也能集中在一处配置。
  3. 内部服务完全不要发布端口。数据库和缓存只需要 Compose 网络,同一项目的容器可通过服务名互相访问(db:3306)。

为应用配置一个最简单的 Nginx 站点(HTTPS 可按Nginx 与 Certbot 教程用 Let's Encrypt 添加):

# /etc/nginx/sites-available/app.example.com  (Rocky/Alma: /etc/nginx/conf.d/app.conf)
server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

检查实际对外暴露了哪些端口:

# On the VPS: what is published, and on which address?
docker ps --format '{{.Names}}\t{{.Ports}}'
ss -tlnp | grep docker-proxy

# From ANOTHER machine: this must fail for a port bound to 127.0.0.1
nc -vz 203.0.113.10 8080

如果某个端口确实必须公开,但只允许特定地址访问,可以在 DOCKER-USER 链中添加规则,Docker 会先于自己的规则检查这条链。--ctorigdstport 匹配的是客户端发送时的原始端口,也就是 Docker 地址转换之前的端口:

# Allow new connections to published port 8080 only from 203.0.113.0/24
iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.0/24 -p tcp \
  -m conntrack --ctstate NEW --ctorigdstport 8080 -j DROP
iptables -L DOCKER-USER -n -v --line-numbers
DOCKER-USER 中的规则不会自动保存,重启后就会丢失,写错规则还可能让容器断网。绑定到 127.0.0.1 更简单,重启后依然有效。主机防火墙本身的配置请参阅 UFW 与 firewalld 防火墙教程。

步骤 5:保持磁盘整洁

在 10–40 GB 的硬盘上,旧镜像、已停止的容器和构建缓存很快就会堆积起来。先看看 Docker 占用了多少空间:

docker system df          # images, containers, volumes, build cache
docker system df -v       # per image / container / volume
df -h / && du -sh /var/lib/docker

然后由安全到谨慎,逐步清理:

# Safe: stopped containers, unused networks, dangling images, build cache
docker system prune

# Unused images (not used by any container) older than 7 days
docker image prune -a --filter "until=168h"

# Build cache only
docker builder prune

# CAREFUL: deletes volumes not used by any container - this is data
docker volume ls
docker volume prune
docker volume prune 和 docker system prune --volumes 会删除当时没有被任何容器使用的卷。如果你停止并删除了数据库容器,它的卷就算作未使用,数据会随之消失。务必先执行 docker volume ls,并备份需要的内容。

每周自动清理一次,且永远不碰数据卷:

# /etc/cron.d/docker-prune - weekly, never touches volumes
0 4 * * 0 root docker image prune -af --filter "until=168h" >/dev/null && docker builder prune -f --filter "until=168h" >/dev/null

如果硬盘已经满了,请参阅如何查找并释放磁盘空间。

步骤 6:在 512 MB 和 1 GB 方案上使用 swap 或 zram

多个容器同时出现内存峰值时,少量 swap 可以避免小内存 VPS 突然因内存不足而杀进程。经典做法是在硬盘上建 swap 文件(参阅如何添加 swap 空间)。在 Debian 和 Ubuntu 上,还可以加上 zram:它把压缩后的 swap 放在内存里,速度快得多,也不产生磁盘 I/O:

# Debian / Ubuntu: compressed swap in RAM
apt install -y zram-tools
cat > /etc/default/zramswap <<'EOF'
ALGO=zstd
PERCENT=50
PRIORITY=100
EOF
systemctl restart zramswap
swapon --show

# Prefer RAM, swap only under pressure
echo 'vm.swappiness=10' > /etc/sysctl.d/90-swappiness.conf
sysctl --system

Rocky Linux 和 AlmaLinux 请使用 swap 文件。swap 只是安全网,不是额外内存:如果 free -h 显示 swap 一直在被使用,说明当前方案对你的容器来说太小了。

步骤 7:安全地更新容器

固定镜像版本,不要用 :latest。这样只有在你决定时才会更新,而且随时知道该回滚到哪个版本:

cd /opt/app
# 1. Back up first (database dump; replace "app" with your database)
docker compose exec -T db sh -c 'mariadb-dump -uroot -p"$MARIADB_ROOT_PASSWORD" --single-transaction app' > /root/app-$(date +%F).sql

# 2. Change the image tag in compose.yaml (e.g. 1.4.2 -> 1.5.0), then:
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=50 app

# 3. Roll back if needed: put the old tag back and run "docker compose up -d"

Docker 引擎本身随系统软件包一起更新。有了 live-restore,升级期间容器会继续运行:

apt-get update && apt-get install --only-upgrade -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Rocky / AlmaLinux
dnf upgrade -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
数据库和有状态应用不要使用自动更新工具:新的大版本可能改变数据格式。先看更新说明、做好备份,再手动更新。大型镜像请在自己的电脑或 CI 中构建后拉取,不要在 1 GB 的 VPS 上构建。

FAQ

512 MB 方案够跑 Docker 吗?

跑一两个轻量容器(静态网站、小型代理或机器人)是够的,前提是配好 swap 并严格限制内存。数据库加 Web 应用则建议使用 1–2 GB 方案,会宽裕很多。

容器为什么不停地重启?

查看 docker compose logs app 和 docker inspect $(docker compose ps -q app) --format '{{.State.OOMKilled}}'。如果结果是 true,说明容器触及了内存上限:可以调高 mem_limit、降低应用的内存占用,或在服务页面升级方案。

JUSTG 能帮忙处理容器问题吗?

VPS 本身的问题,例如网络或控制台访问,请通过工单联系 Technical support。JUSTG 提供 7×24 小时 AI 客服(工单),需要人工处理的问题会由工程师跟进。

这篇文章有帮助吗?
帮助中心
Linux 服务器 Linux SSH 密钥登录教程:设置 ed25519 密钥并禁用密码登录 只要服务器有公网 IPv4,上线几分钟内就会遇到 SSH 暴力破解机器人,而密码登录正是它们的首要目标。本文说明如何用较新的 ed25519 密钥完成 Linux SSH 密钥登录,再禁用 SSH 密码登录,全程避免把自己锁在门外。步骤适用于 Debian… Linux 服务器 Linux VPS 修改 SSH 端口与 root 密码教程(安全不断线) 把 SSH 从 22 端口移走虽然挡不住有心的攻击者,却能让日志里大部分自动扫描消失;而任何新服务器的第一件事,就是换一组新的 root 密码。本文说明如何在 Linux VPS 修改 SSH 端口与 root 密码且不断线:修改 sshd_config、在 Rocky… Linux 服务器 Linux 防火墙设置教程:VPS 使用 UFW 与 firewalld(含 IPv6) 新安装的 Linux 服务器,任何服务打开的端口都会直接对外响应,因此主机防火墙是最先要设置的项目之一。本文说明如何在 Debian / Ubuntu 用 UFW 设置 Linux 防火墙,以及在 CentOS、Rocky Linux、AlmaLinux 使用… Linux 服务器 Linux VPS 添加 Swap 交换空间教程(适合 512MB / 1GB 小内存套餐) 在 512 MB 或 1 GB 的云服务器上,数据库、PHP-FPM 加上一次软件包更新,很容易在同一时间用光内存。这时内核的 OOM killer 会强制终止进程,最常被牺牲的就是 MySQL 或 MariaDB。在 Linux VPS 添加 Swap… Linux 服务器 Linux 硬盘空间已满怎么办?VPS 找出并释放磁盘空间教程 “No space left on device”几乎会让服务器上所有东西出问题:数据库无法写入、网站返回错误、软件包更新失败,有时甚至无法正常登录。本文一步步说明如何排除 Linux 硬盘空间已满:先确认是数据块还是 inode 用完,再用 du 与 ncdu…
AlipayUnionPayVISAMastercardPayPalUSDTstripe