(2) 틈새 소프트웨어의 정점 만들기: The devcontainer
Source: Dev.to
Overview
I prefer an ultra‑stable, shareable, and reproducible environment, so my go‑to solution is a devcontainer — see the official docs.
Until recently I treated the .devcontainer folder as an intrinsic part of the project, containing the single “true” development container setup. Developers could bring their own custom configs via dotfiles.
My perspective has shifted. Now I add a folder with my own name inside .devcontainer and let each developer decide how they want to extend it.
Below is my current favorite setup for Elm development in the tech stack described in my earlier post. It involves a few moving parts, so hold on!
기본 컨테이너 구축
내 요구에 맞게 맞춤화된 기본 컨테이너를 구축하는 리포지토리는 다음과 같습니다:
참고: 이름은 내 작업 흐름에 특화된 것이지만, 자유롭게 영감을 얻으세요.
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에 이 설정을 사용하면 컨테이너를 프로덕션에 배포할 때 매우 자신감이 생깁니다—프로덕션에서 버그가 이제는 드물게 발생합니다.
다음 단계에서는 vite-plugin-elm-watch를 설정하는 방법에 초점을 맞출 것입니다, 이는…