On this page
Deno 和 Docker
Docker 是打包和分发 Deno 应用的一种常见方式:你只需构建一次可复现的镜像,然后在任何地方以相同方式运行它。Deno 提供官方基础镜像,因此 Dockerfile 通常很简短。本页涵盖编写该 Dockerfile、通过多阶段构建保持镜像精简,以及在生产环境中常用的 Compose、工作区、安全性和健康检查配置。
在 Docker 中使用 Deno Jump to heading
Deno 提供了官方 Docker 文件 和镜像。
要使用官方镜像,请在项目目录中创建一个 Dockerfile:
FROM denoland/deno:latest
WORKDIR /app
# 先复制清单文件,以便依赖安装层在仅源代码变更时仍可缓存
COPY deno.json deno.lock package.json* ./
RUN deno ci --prod --skip-types
# 然后复制其余源码
COPY . .
CMD ["deno", "run", "--allow-net", "main.ts"]
deno ci 会从 deno.lock 执行可复现安装。--prod 会跳过 devDependencies,--skip-types 会移除 @types/* 包。这两者都能在不影响运行时行为的前提下减小最终镜像体积。
最佳实践 Jump to heading
使用多阶段构建 Jump to heading
用于更小的生产镜像:
# 构建阶段
FROM denoland/deno:latest AS builder
# 将 Deno 的缓存指向一个已知位置,以便在下一阶段复制
ENV DENO_DIR=/deno-dir
WORKDIR /app
# 先复制清单文件,以便依赖安装层在仅源代码变更时仍可缓存
COPY deno.json deno.lock package.json* ./
RUN deno ci --prod --skip-types
# 然后复制其余源码
COPY . .
# 生产阶段
FROM denoland/deno:latest
ENV DENO_DIR=/deno-dir
WORKDIR /app
COPY --from=builder /app .
# 复制已填充的 Deno 缓存,以便运行时阶段拥有这些依赖
COPY --from=builder /deno-dir /deno-dir
CMD ["deno", "run", "--allow-net", "main.ts"]
如果不复制 $DENO_DIR,deno ci 只会写入构建阶段内 Deno 的全局缓存。这些文件不会随着 COPY --from=builder /app . 一起传到后续阶段,因此容器会在首次运行时重新下载依赖。
权限标志 Jump to heading
明确指定所需权限:
CMD ["deno", "run", "--allow-net=api.example.com", "--allow-read=/data", "main.ts"]
开发容器 Jump to heading
用于支持热重载的开发:
FROM denoland/deno:latest
WORKDIR /app
COPY . .
CMD ["deno", "run", "--watch", "--allow-net", "main.ts"]
常见问题与解决方案 Jump to heading
-
权限被拒绝(Permission Denied)错误
- 使用
--allow-*标志 - 考虑使用
deno.json权限
- 使用
-
镜像体积过大
- 使用多阶段构建
- 仅包含必要文件
- 添加合适的
.dockerignore
-
缓存失效(Cache Invalidation)
- 分离依赖缓存
- 使用正确的镜像层顺序
示例 .dockerignore Jump to heading
.git
.gitignore
Dockerfile
README.md
*.log
_build/
node_modules/
可用的 Docker 标签 Jump to heading
Deno 提供了多个官方标签:
denoland/deno:latest- 最新稳定版denoland/deno:alpine- 基于 Alpine 的更小镜像denoland/deno:distroless- 基于 Google distroless 的镜像denoland/deno:ubuntu- 基于 Ubuntu 的镜像denoland/deno:2.x- 锁定到特定发布线(使用你 目标版本)
环境变量 Jump to heading
Docker 中常见的 Deno 环境变量:
ENV DENO_DIR=/deno-dir/
ENV DENO_INSTALL_ROOT=/usr/local
ENV PATH=${DENO_INSTALL_ROOT}/bin:${PATH}
# 可选环境变量
ENV DENO_NO_UPDATE_CHECK=1
ENV DENO_NO_PROMPT=1
在 Docker 中运行测试 Jump to heading
FROM denoland/deno:latest
WORKDIR /app
COPY . .
# 运行测试
CMD ["deno", "test", "--allow-none"]
使用 Docker Compose Jump to heading
用于带热重载的开发的基础 Compose 文件:
services:
deno-app:
build: .
volumes:
- .:/app
ports:
- "8000:8000"
environment:
- DENO_ENV=development
command: ["deno", "run", "--watch", "--allow-net", "main.ts"]
对于带数据库的更真实场景:
services:
app:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://deno:${POSTGRES_PASSWORD}@db:5432/app
depends_on:
db:
condition: service_healthy
restart: unless-stopped
command:
[
"deno",
"run",
"--allow-net=db:5432",
"--allow-env=DATABASE_URL",
"main.ts",
]
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: deno
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U deno"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
volumes:
pgdata:
将诸如 POSTGRES_PASSWORD 之类的密钥放在与
docker-compose.yml 相邻的 .env 文件中。Compose 会自动加载它。不要提交该文件。
使用 docker compose up 启动服务,或者使用
docker compose up -d 在后台运行。
健康检查 Jump to heading
HEALTHCHECK --interval=30s --timeout=3s \
CMD deno eval "try { await fetch('http://localhost:8000/health'); } catch { Deno.exit(1); }"
常见开发工作流 Jump to heading
本地开发:
- 构建镜像:
docker build -t my-deno-app . - 使用数据卷挂载运行:
docker run -it --rm \
-v ${PWD}:/app \
-p 8000:8000 \
my-deno-app
安全注意事项 Jump to heading
- 以非 root 用户运行:
# 创建 deno 用户
RUN addgroup --system deno && \
adduser --system --ingroup deno deno
# 切换到 deno 用户
USER deno
# 继续 Dockerfile 的其余部分
- 使用最小权限:
CMD ["deno", "run", "--allow-net=api.example.com", "--allow-read=/app", "main.ts"]
- 考虑使用
--deny-*标志以增强安全性
在 Docker 中使用工作区 Jump to heading
当在 Docker 中处理 Deno 的工作区(monorepos,单仓多包)时,主要有两种方案:
1. 完整工作区容器化 Jump to heading
当你需要所有依赖时,将整个工作区包含进去:
FROM denoland/deno:latest
WORKDIR /app
# 复制整个工作区
COPY deno.json .
COPY project-a/ ./project-a/
COPY project-b/ ./project-b/
WORKDIR /app/project-a
CMD ["deno", "run", "-A", "mod.ts"]
2. 最小化工作区容器化 Jump to heading
对于更小的镜像,只包含所需的工作区成员:
- 创建构建上下文目录结构:
project-root/
├── docker/
│ └── project-a/
│ ├── Dockerfile
│ ├── .dockerignore
│ └── build-context.sh
├── deno.json
├── project-a/
└── project-b/
- 创建一个
.dockerignore:
*
!deno.json
!project-a/**
!project-b/** # 仅在需要时
- 创建构建上下文脚本:
#!/bin/bash
# 创建临时构建上下文
BUILD_DIR="./tmp-build-context"
mkdir -p $BUILD_DIR
# 复制工作区配置
cp ../../deno.json $BUILD_DIR/
# 复制主项目
cp -r ../../project-a $BUILD_DIR/
# 仅复制所需依赖
if grep -q "\"@scope/project-b\"" "../../project-a/mod.ts"; then
cp -r ../../project-b $BUILD_DIR/
fi
- 创建一个最小的 Dockerfile:
FROM denoland/deno:latest
WORKDIR /app
# 复制必要文件
COPY deno.json .
COPY project-a/ ./project-a/
# 仅在需要时复制依赖
COPY project-b/ ./project-b/
WORKDIR /app/project-a
CMD ["deno", "run", "-A", "mod.ts"]
- 构建容器:
cd docker/project-a
./build-context.sh
docker build -t project-a -f Dockerfile tmp-build-context
rm -rf tmp-build-context
最佳实践 Jump to heading
- 始终包含根目录下的
deno.json文件 - 保持与开发时相同的目录结构
- 清晰记录工作区依赖
- 使用构建脚本管理上下文
- 仅包含所需的工作区成员
- 当依赖发生变化时更新
.dockerignore