(2)打造小众软件的巅峰:devcontainer

发布: (2025年12月23日 GMT+8 05:31)
6 min read
原文: Dev.to

Source: Dev.to

请提供您想要翻译的具体文本内容(除代码块和 URL 之外),我将按照要求将其翻译为简体中文并保留原有的格式。

Overview

我更倾向于使用超稳定、可共享且可复现的环境,因此我的首选方案是 devcontainer — 请参阅官方文档。

直到最近,我一直把 .devcontainer 文件夹视为项目的固有部分,里面只包含唯一的“真实”开发容器设置。开发者可以通过 dotfiles 带入自己的自定义配置。

我的观点已经转变。现在,我在 .devcontainer 中添加一个以我自己名字命名的文件夹,让每位开发者自行决定如何扩展它。

下面是我目前最喜欢的 Elm 开发环境设置,适用于我之前帖子中描述的技术栈。它涉及多个移动部件,请耐心阅读!

构建基础容器

构建基础容器(根据我的需求定制)的仓库是:

注意: 该名称特定于我的工作流,但欢迎借鉴。

devcontainer 设置

文件夹结构

project-root/
├── .devcontainer/
│   ├── vanilla/                 # Basic example
│   └── theodor/                 # Personal environment config
│       ├── ssl/
│       │   ├── squidex.crt
│       │   └── squidex.key
│       ├── devcontainer.json
│       ├── docker-compose.yml
│       ├── Dockerfile
│       ├── Dockerfile.omnia
│       └── nginx.conf
├── backend/
├── backup/
├── build/
├── Documentation/
├── dotnet/
└── elm/

我正在考虑将 theodor 添加到 .gitignore,但尚未决定。

注释

  • 此设置相当高级!
  • 在本地使用自定义域名——而不是 localhost:port
  • 在类似生产环境的配置下运行 nginx
  • 通过 nginx 和 site‑config‑loader 工具设置环境变量。

devcontainer.json

{
  "name": "Dotnet 9/10 and Elm Dev Container (Debian + Compose)",
  "dockerComposeFile": "docker-compose.yml",
  "service": "dev",
  "workspaceFolder": "/workspace",
  "mounts": [
    "source=ktk-elm-devcontainer,target=/home/container-user/.elm,type=volume"
  ],
  "customizations": {
    "vscode": {
      "settings": {
        "terminal.integrated.defaultProfile.linux": "zsh"
      },
      "extensions": [
        "ms-vscode-remote.remote-containers",
        "Elmtooling.elm-ls-vscode",
        "ms-dotnettools.csharp",
        "william-voyek.vscode-nginx",
        "vscodevim.vim",
        "ms-dotnettools.csdevkit",
        "EditorConfig.EditorConfig",
        "humao.rest-client",
        "esbenp.prettier-vscode",
        "DotJoshJohnson.xml",
        "streetsidesoftware.code-spell-checker",
        "streetsidesoftware.code-spell-checker-danish",
        "bradlc.vscode-tailwindcss",
        "kamikillerto.vscode-colorize",
        "Ionide.Ionide-fsharp",
        "ms-azuretools.vscode-containers",
        "jebbs.plantuml",
        "task.vscode-task",
        "ecmel.vscode-html-css"
      ]
    }
  },
  "remoteUser": "container-user",
  "portsAttributes": {
    "3033": { "label": "Elm" },
    "5130": { "label": "Backend" },
    "5140": { "label": "Gateway" },
    "8314": { "label": "Nginx" },
    "8376": { "label": "Squidex" },
    "9876": { "label": "Elm" }
  }
}

docker-compose.yml

services:
  # 主要开发服务
  dev:
    # 为此服务构建镜像
    build:
      context: .
      dockerfile: Dockerfile

    # 要挂载的卷
    volumes:
      - ../..:/workspace:cached
      - ktk-elm-devcontainer:/home/container-user/.elm

      # --- Neovim 配置的替代方案 ---
      # - ../.nvim:/root/.config/nvim:cached

      # --- 添加此行 ---
      # - ./.config/nvim:/root/.config/nvim:cached

    # command: ["sleep", "infinity"]

保持容器运行

command: sleep infinity
networks:
  - internal

服务

nginx:
  image: nginx:alpine
  container_name: ktk_nginx
  ports:
    - "80:80"
    - "443:443"
    - "8314:80"
  networks:
    - internal
  volumes:
    - ./nginx.conf:/etc/nginx/nginx.conf:ro
    - ./ssl:/etc/nginx/ssl:ro
  extra_hosts:
    - "host.docker.internal:host-gateway"
  depends_on:
    - dev
    - squidex

mongo:
  image: "mongo:6"
  volumes:
    - ktk_mongo_data:/data/db
  networks:
    - internal
  restart: unless-stopped

squidex:
  image: "squidex/squidex:7"
  ports:
    - "8376:5000"
  environment:
    - URLS__BASEURL=https://squidex.ktk.dk
    - IDENTITY__ALLOWHTTPSCHEME=false
    - EVENTSTORE__MONGODB__CONFIGURATION=mongodb://mongo
    - STORE__MONGODB__CONFIGURATION=mongodb://mongo
    - IDENTITY__ADMINEMAIL=sukkerfrit@gmail.com
    - IDENTITY__ADMINPASSWORD=0hSoS3cret!
    - ASPNETCORE_URLS=http://+:5000
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:5000/healthz"]
    start_period: 60s
  depends_on:
    - mongo
  volumes:
    - ktk_squidex_assets:/app/Assets
  networks:
    - internal
  restart: unless-stopped

volumes:
  ktk-elm-devcontainer:
  ktk_squidex_assets:
  ktk_mongo_data:

网络

networks:
  internal:
    driver: bridge

Dockerfile

FROM isuperman/elm-devcontainer-foundation:0.1.7
# Project specifics could be added here

生产环境般的本地开发

此配置让您能够测试路由、陷阱(tarpit) 等功能。使用自定义域名进行本地开发,使环境与生产环境非常接近。

nginx.conf

worker_processes 1;
events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    resolver 127.0.0.11;

    server {
        listen 80;
        server_name localhost ktk.dk;

        location /api/ {
            set $backend_upstream host.docker.internal:5130;
            rewrite ^/api/(.*)$ /$1 break;
            proxy_pass http://$backend_upstream;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }

        location /cms/ {
            set $backend_upstream host.docker.internal:5140;
            rewrite ^/cms/(.*)$ /$1 break;
            proxy_pass http://$backend_upstream;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }

        location / {
            set $frontend_upstream host.docker.internal:3033;
            proxy_pass http://$frontend_upstream;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            sub_filter '' '';
            sub_filter_once on;
        }

        location = /robots.txt {
            set $frontend_upstream host.docker.internal:3033;
            proxy_pass http://$frontend_upstream/robots.txt;
            proxy_http_version 1.1;
            proxy_set_header Host $host;
        }

        location = /humans.txt {
            set $frontend_upstream host.docker.internal:3033;
            proxy_pass http://$frontend_upstream/humans.txt;
            proxy_http_version 1.1;
            proxy_set_header Host $host;
        }
    }

    server {
        listen 80;
        server_name squidex.ktk.dk;
        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl;
        server_name squidex.ktk.dk www.squidex.ktk.dk;

        ssl_certificate /etc/nginx/ssl/squidex.crt;
        ssl_certificate_key /etc/nginx/ssl/squidex.key;
        ssl_protocols TLSv1.2 TLSv1.3;

        location / {
            proxy_pass http://squidex:5000;
            proxy_http_version 1.1;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_cookie_path / /;
        }
    }
}

结论

本示例展示了如何使用 devcontainer 来创建一个与生产环境相匹配的开发环境。通过在本地 docker‑compose.yml 中使用这些设置,我在将容器推送到生产环境时非常有信心——生产环境中的 bug 已经变得极为罕见。

接下来我们将重点介绍如何设置 vite-plugin-elm-watch,它将…

Back to Blog

相关文章

阅读更多 »