开发技巧

VS Code Dev Containers:搭建 FastAPI + PostgreSQL 开发环境

用 VS Code 的 Dev Containers 扩展搭一个“打开就能写代码”的开发环境:Python 3.12 + FastAPI + PostgreSQL 17 全部跑在容器里,新同事克隆仓库、点一下“在容器中重新打开”,几分钟后就能运行和调试项目。

BINBHO · 图文教程

用 VS Code 的 Dev Containers 扩展搭一个“打开就能写代码”的开发环境:Python 3.12 + FastAPI + PostgreSQL 17 全部跑在容器里,新同事克隆仓库、点一下“在容器中重新打开”,几分钟后就能运行和调试项目。

先看清成品

成品是一个待办事项 API 仓库,里面带 .devcontainer/ 配置。打开后 VS Code 自动拉起两个容器(应用和数据库),装好 Python 扩展和依赖,转发 8000 端口。本文的配置已在本地用官方 Dev Containers CLI 0.89.0 实际启动,容器里测试通过,接口返回正确数据。

做完之后你会得到什么?

成果 说明
.devcontainer/devcontainer.json VS Code 读取的环境定义
.devcontainer/compose.yaml 应用容器 + PostgreSQL 容器
待办事项 API GET/POST /todos、GET /health
调试配置 F5 直接断点调试 FastAPI
自动化测试 在容器里运行 pytest

需要准备哪些环境?

项目 要求 说明
VS Code 最新稳定版 官网下载
Dev Containers 扩展 ms-vscode-remote.remote-containers 扩展市场搜索 Dev Containers
Docker Docker Desktop(Windows/macOS)或 Docker Engine(Linux) 需要支持 docker compose
磁盘空间 预留几 GB 首次会下载 Python 和 PostgreSQL 镜像

Windows 用户建议在 Docker Desktop 里启用 WSL 2 后端,并把项目放在 WSL 文件系统里,文件读写会快很多。

注意:本机不需要安装 Python 和 PostgreSQL,这正是 Dev Container 的意义。

第一步:项目结构是什么样的?

todo-api/
├── .devcontainer/
│   ├── devcontainer.json
│   └── compose.yaml
├── .vscode/
│   └── launch.json
├── app/
│   ├── __init__.py
│   └── main.py
├── tests/
│   └── test_api.py
└── requirements.txt

1. 让 AI 帮你生成初稿(可选)

在 VS Code 的 Copilot Chat 或其他 AI 助手里发送:

帮我为一个 FastAPI + PostgreSQL 项目写 Dev Container 配置:
- 使用 docker compose,两个服务 app 和 db
- app 用 mcr.microsoft.com/devcontainers/python:3.12 镜像
- db 用 postgres:17,带 healthcheck,app 等 db 健康后再启动
- 安装 Python、debugpy、Ruff 扩展,转发 8000 端口
- 创建容器后自动安装 requirements.txt

AI 给出的版本要和下面的完整配置对照检查,尤其是 workspaceFolder 和挂载路径。

第二步:怎样写 Dev Container 配置?

1. devcontainer.json

{
  "name": "todo-api",
  "dockerComposeFile": "compose.yaml",
  "service": "app",
  "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
  "features": {
    "ghcr.io/devcontainers/features/github-cli:1": {}
  },
  "forwardPorts": [8000],
  "portsAttributes": {
    "8000": { "label": "API", "onAutoForward": "openBrowser" }
  },
  "postCreateCommand": "pip install --user -r requirements.txt",
  "customizations": {
    "vscode": {
      "extensions": ["ms-python.python", "ms-python.debugpy", "charliermarsh.ruff"],
      "settings": {
        "python.testing.pytestEnabled": true,
        "editor.formatOnSave": true
      }
    }
  },
  "remoteUser": "vscode"
}
字段 作用
dockerComposeFile / service 用 Compose 启动,VS Code 连接 app 服务
workspaceFolder 容器里打开的目录
features 额外工具,这里加了 GitHub CLI
forwardPorts 把容器的 8000 端口转发到本机
postCreateCommand 容器第一次创建后执行,安装依赖
customizations.vscode 容器里自动装的扩展和设置
remoteUser 用非 root 的 vscode 用户工作

2. compose.yaml

services:
  app:
    image: mcr.microsoft.com/devcontainers/python:3.12
    volumes:
      - ../..:/workspaces:cached
    command: sleep infinity
    environment:
      DATABASE_URL: postgresql://todo:todo@db:5432/todo
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: todo
      POSTGRES_PASSWORD: todo
      POSTGRES_DB: todo
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U todo -d todo"]
      interval: 5s
      retries: 10

volumes:
  pgdata:

volumes: ../..:/workspaces 把项目的上级目录挂进容器,配合 workspaceFolder 就能打开项目本身。sleep infinity 让应用容器常驻,由你在里面手动启动服务。

3. requirements.txt

fastapi[standard]==0.156.4
psycopg[binary]==3.3.6
pytest==9.1.1

第三步:应用代码怎么写?

1. app/main.py

import os
from contextlib import asynccontextmanager

import psycopg
from fastapi import FastAPI
from pydantic import BaseModel

DB = os.environ.get("DATABASE_URL", "postgresql://todo:todo@localhost:5432/todo")


def conn():
    return psycopg.connect(DB, autocommit=True)


@asynccontextmanager
async def lifespan(app: FastAPI):
    with conn() as c:
        c.execute("CREATE TABLE IF NOT EXISTS todo (id serial PRIMARY KEY, title text NOT NULL, done boolean DEFAULT false)")
    yield


app = FastAPI(title="Todo API", lifespan=lifespan)


class TodoIn(BaseModel):
    title: str


@app.get("/health")
def health():
    with conn() as c:
        c.execute("SELECT 1")
    return {"status": "ok"}


@app.post("/todos", status_code=201)
def create(t: TodoIn):
    with conn() as c:
        row = c.execute("INSERT INTO todo (title) VALUES (%s) RETURNING id, title, done", (t.title,)).fetchone()
    return {"id": row[0], "title": row[1], "done": row[2]}


@app.get("/todos")
def list_todos():
    with conn() as c:
        rows = c.execute("SELECT id, title, done FROM todo ORDER BY id").fetchall()
    return [{"id": r[0], "title": r[1], "done": r[2]} for r in rows]

数据库地址来自 Compose 注入的 DATABASE_URL 环境变量,主机名 db 就是 Compose 里的服务名。

2. tests/test_api.py

from fastapi.testclient import TestClient

from app.main import app


def test_create_and_list():
    with TestClient(app) as client:
        assert client.get("/health").json() == {"status": "ok"}
        r = client.post("/todos", json={"title": "写 Dev Container 教程"})
        assert r.status_code == 201
        titles = [t["title"] for t in client.get("/todos").json()]
        assert "写 Dev Container 教程" in titles

注意:这个测试直接写入开发数据库,只适合演示。正式项目应单独准备测试库,或在测试里回滚事务。

3. .vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "FastAPI",
      "type": "debugpy",
      "request": "launch",
      "module": "uvicorn",
      "args": ["app.main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"],
      "jinja": true
    }
  ]
}

第四步:怎样在容器里打开并运行?

1. 在容器中重新打开

用 VS Code 打开 todo-api 文件夹,按 Ctrl+Shift+P(macOS 为 Cmd+Shift+P),运行:

Dev Containers: Reopen in Container

第一次会下载镜像并执行 postCreateCommand,右下角可以点开查看日志。

2. 检查结果

检查项 通过标准
左下角 显示 Dev Container: todo-api
终端 提示符用户是 vscode
扩展 Python、Ruff 已在容器里安装
数据库 终端里 getent hosts db 能解析出地址

3. 运行测试

在 VS Code 终端里执行:

python -m pytest -q

本文实际运行结果:

1 passed in 0.39s

4. 断点调试

在 create() 函数里点一个断点,按 F5 选择“FastAPI”。再在终端里发请求:

curl -s -X POST localhost:8000/todos -H "content-type: application/json" -d '{"title":"买咖啡豆"}'

程序会停在断点处。继续运行后返回:

{"id":2,"title":"买咖啡豆","done":false}

本机浏览器打开 http://localhost:8000/docs 能看到 FastAPI 自动生成的接口文档,这是端口转发在起作用。

第五步:不用 VS Code 也能启动吗?

可以。官方提供 Dev Containers CLI,适合在 CI 里复用同一套环境:

npm install -g @devcontainers/cli
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . python -m pytest -q

本文就是用这个 CLI 在没有图形界面的 Linux 上验证的,up 成功后输出里包含 "outcome":"success"。

出问题时该怎么调?

1. 报错 compose build requires buildx 0.17.0 or later

本文验证时就遇到了这个错误:Dev Containers 会调用 docker compose build,而系统自带的 buildx 太旧。

升级 Docker Desktop,或在 Linux 上安装新版 buildx 插件后重试。用 docker buildx version 确认版本。

2. postCreateCommand 安装依赖失败

错误日志里出现 Temporary failure in name resolution,说明容器里解析不了域名。

先在本机执行 docker run --rm busybox nslookup pypi.org 检查 Docker 网络。公司网络下常见原因是代理或防火墙,需要在 Docker 设置里配置代理。

修好后运行 Dev Containers: Rebuild Container 重新创建。

3. 应用连不上数据库

症状 检查
connection timeout 容器间网络被防火墙规则拦截,docker compose ps 看两个容器是否在同一网络
could not translate host name "db" DATABASE_URL 主机名要写服务名 db,不是 localhost
password authentication failed 改过密码但数据卷还是旧的,见下一条

4. 修改了数据库密码不生效

PostgreSQL 只在数据卷为空时初始化。删除卷后重建:

docker compose -p todo-api_devcontainer -f .devcontainer/compose.yaml down -v

注意:down -v 会删除数据库里的所有数据。

常见问题怎么处理?

现象 原因 解决方法
找不到 Reopen in Container 没装 Dev Containers 扩展 扩展市场安装 ms-vscode-remote.remote-containers
修改 devcontainer.json 后没变化 容器已创建 运行 Rebuild Container
Windows 上文件操作很慢 项目在 Windows 文件系统 把项目放进 WSL 目录
端口 8000 打不开 服务只监听容器内 127.0.0.1 启动参数加 --host 0.0.0.0,或依赖 VS Code 自动转发
docker: permission denied Linux 用户不在 docker 组 按 Docker 官方文档配置非 root 用户
扩展没自动安装 扩展 ID 写错 在扩展详情页复制准确 ID

项目资源

用途 地址
Dev Containers 文档 https://code.visualstudio.com/docs/devcontainers/containers
devcontainer.json 规范 https://containers.dev/implementors/json_reference/
Dev Container Features https://containers.dev/features
Dev Containers CLI https://github.com/devcontainers/cli
Python 开发镜像 https://github.com/devcontainers/images/tree/main/src/python
Docker Desktop https://docs.docker.com/desktop/
PostgreSQL 镜像 https://hub.docker.com/_/postgres
FastAPI 文档 https://fastapi.tiangolo.com/

建议先只做到第四步的“在容器中重新打开”,确认左下角显示容器名、getent hosts db 有结果。

环境能跑通后,再把自己的真实项目迁移进来,并把 .devcontainer/ 提交到 Git,让团队所有人共用。