简介
Docker Compose 让你能够在一个声明式文件中定义一个多容器应用——一个 Web 应用、它的数据库、一个缓存以及一个反向代理——然后用一条命令把它们全部启动起来。你无需手动运行冗长的 docker run 命令,只需描述一次期望状态,Compose 就会为你完成协调。
在本教程中,你将在一台 Linux 服务器上部署一个虽小但贴近实际的技术栈:一个 Web 应用、一个 PostgreSQL 数据库,以及一个自动终止 HTTPS 的 Caddy 反向代理。完成后,你将拥有一个可通过你自己的域名经由 TLS 访问的运行中应用。
我们将部署在 Skyline Cloud VPS 上。由于 Skyline 在沙特阿拉伯境内运营本地基础设施,你的数据以及你客户的数据都将处于 PDPL、NCA 和 SDAIA 的管辖范围之内——当你为沙特和海湾合作委员会(GCC)的组织托管工作负载时,这一点至关重要。如果你还没有服务器,几分钟即可创建一台。
前置条件
- 一台拥有公网 IP 的 Linux 服务器(Ubuntu 22.04/24.04 或 Debian 12)。一台 1–2 vCPU / 2 GB 内存的 VPS 足以入门。
- 一个具备
sudo权限的非 root 用户,以及对服务器的 SSH 访问权限。 - 一个域名,其 A 记录 指向你服务器的公网 IP。使用 Skyline 提供的 .sa 域名和托管 DNS,创建一条类似
app.example.sa → 203.0.113.10的记录。
第 1 步 — 安装 Docker Engine 与 Compose 插件
现代 Docker 将 Compose 作为插件提供(使用 docker compose,而非旧版的 docker-compose)。官方的便捷脚本会一并安装引擎和插件:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
将你的用户加入 docker 组,这样无需 sudo 即可运行 Docker,然后重新打开会话:
sudo usermod -aG docker $USER
newgrp docker
验证引擎和 Compose 插件是否都已就绪:
docker --version
docker compose version
第 2 步 — 规划项目结构
为你的技术栈创建一个目录并进入其中:
mkdir -p ~/myapp && cd ~/myapp
我们将在这里保留三个文件:用于构建应用镜像的 Dockerfile、用于将各服务连接起来的 compose.yaml,以及用于存放密钥和配置的 .env 文件。
第 3 步 — 编写应用的 Dockerfile
对于一个典型的 Node.js 应用,一个小巧的多阶段构建可以让最终镜像保持精简。请根据你自己的技术栈(Python、Go、PHP 等)调整这些命令:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
第 4 步 — 在 .env 文件中定义密钥
切勿在 compose 文件中硬编码凭据。把它们放进 .env 文件,Compose 会自动读取:
POSTGRES_USER=appuser
POSTGRES_PASSWORD=change-me-to-a-strong-secret
POSTGRES_DB=appdb
APP_DOMAIN=app.example.sa
限制其权限,并将其排除在版本控制之外:
chmod 600 .env
echo ".env" >> .gitignore
第 5 步 — 编写 Compose 文件
创建 compose.yaml。它定义了三个服务、一个私有网络,以及命名卷,从而让你的数据和 TLS 证书在重启后依然保留:
services:
app:
build: .
restart: unless-stopped
environment:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
depends_on:
db:
condition: service_healthy
expose:
- "3000"
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
- app
volumes:
db-data:
caddy-data:
caddy-config:
有几个细节值得留意:
- 数据库端口没有通过
ports:发布出去;它只能在 Compose 的内部网络上访问,因此永远不会暴露到互联网。 - 带有
condition: service_healthy的depends_on会让应用一直等待,直到 PostgreSQL 真正开始接受连接,而不仅仅是其容器启动完成。 restart: unless-stopped会在重启或崩溃后自动让你的服务恢复运行。
第 6 步 — 配置反向代理
在你的 compose 文件旁边创建一个名为 Caddyfile 的文件。Caddy 会自动获取并续期 Let's Encrypt 证书,因此你无需任何手动证书操作即可获得 HTTPS:
app.example.sa {
reverse_proxy app:3000
}
将 app.example.sa 替换为你真实的域名。Caddy 会通过内部网络把 app 解析到你的应用容器,并将请求代理到 3000 端口。
第 7 步 — 启动技术栈
构建镜像并在后台启动全部服务:
docker compose up -d --build
检查所有服务是否健康:
docker compose ps
跟踪日志,观察应用启动以及 Caddy 签发证书的过程:
docker compose logs -f
在浏览器中打开 https://app.example.sa。此时 Caddy 已经为你预置好了一张有效的 TLS 证书。
第 8 步 — 运维与更新
日常常用命令:
| 任务 | 命令 |
|---|---|
| 查看运行中的服务 | docker compose ps |
| 跟踪某个服务的日志 | docker compose logs -f app |
| 重启某个服务 | docker compose restart app |
| 停止技术栈(保留数据) | docker compose down |
| 拉取更新的基础镜像 | docker compose pull |
| 重新构建并重新部署 | docker compose up -d --build |
要发布新版本的代码,先拉取你的更改,然后运行 docker compose up -d --build。Compose 只会重新创建发生变化的容器,并保持数据库不受影响,从而为无状态服务带来近乎零停机的更新体验。
要移除所有内容包括卷(这会删除你的数据库数据),请运行 docker compose down -v——使用时务必谨慎。
第 9 步 — 备份你的数据
你的应用数据存放在 db-data 卷中。请定期进行转储,并将其存储到服务器之外。一个简单的 PostgreSQL 备份命令如下:
docker compose exec db pg_dump -U appuser appdb > backup-$(date +%F).sql
使用 cron 来定时执行此操作,并将转储文件推送到 Skyline Cloud 对象存储或云备份,这样单台服务器故障就永远不会让你丢失数据。
结语
你现在已经使用 Docker Compose 部署了一个多容器应用,前端有一个自动启用 HTTPS 的反向代理,并运行在一台由你掌控的服务器上。在此基础上,你可以添加一个 Redis 缓存,通过 docker compose up -d --scale app=3 在代理后端扩展无状态服务,或者升级到容器编排——当你的应用超出单台主机的承载能力时,请参阅我们的沙特阿拉伯托管 Kubernetes 指南。
如果你还需要为你的域名配备专业邮箱与你的应用相伴,Skyline 企业邮箱托管同样能让你的邮件保留在境内。
准备好部署了吗?创建你的 Skyline Cloud 服务器,立即开始。
Comments
0 total · 0 threads