golang:1.26-alpine ベースで、VS Code DevContainer からそのまま開発・デバッグ・ホットリロード・マイグレーションまで回す構成です。
1. 前提・バージョン
| 項目 | バージョン / 内容 |
|---|---|
| Go |
go1.26 / image golang:1.26-alpine
|
| DB |
postgres:15-alpine (local port 5433:5432) |
| Backend module |
backend/go.mod (goquery, chromedp, pgx/v5) |
| HotReload |
github.com/air-verse/air@v1.61.1 + .air.toml
|
| Migration | golang-migrate/migrate v4.17.0 |
| Scraping |
chromium + 日本語フォント (font-noto-cjk 等) |
| VS Code拡張 |
golang.go, HashiCorp.terraform, saoudrizwan.claude-dev
|
| Go Tools |
gopls, dlv (postCreateCommandで自動導入) |
2. フォルダ構成
.
├── .devcontainer/
│ ├── devcontainer.json # VS Code DevContainer定義 (service: go)
│ └── docker-compose.yml # DevContainer用オーバーライド (sleep infinity + SYS_PTRACE)
├── docker-compose.yml # 本体: db / api / go サービス定義
└── backend/
├── Dockerfile # api + go 共用 (Air + migrate + Chromium入り)
├── docker-entrypoint.sh # migrate up してから CMD を実行
├── .air.toml # Air設定
├── go.mod
├── go.sum
├── main.go
├── sql/
└── db/
└── migrations/ # *.up.sql / *.down.sql
3. 必要ファイル全文(機密値はマスク済み)
3.1 .devcontainer/devcontainer.json
DevContainerの本体。service: go にアタッチします。
{
"name": "Go Dev Container",
"dockerComposeFile": [
"../docker-compose.yml",
"docker-compose.yml"
],
"service": "go",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"customizations": {
"vscode": {
"extensions": [
"golang.go",
"HashiCorp.terraform",
"saoudrizwan.claude-dev"
],
"settings": {
"go.toolsManagement.autoUpdate": true,
"go.useLanguageServer": true,
"go.gopath": "/go",
"go.goroot": "/usr/local/go"
}
}
},
"postCreateCommand": "go install golang.org/x/tools/gopls@latest && go install github.com/go-delve/delve/cmd/dlv@latest"
}
解説:
-
dockerComposeFileを2段重ねにして本体 + オーバーライドを合成 -
gopls+dlvはコンテナ作成後に自動インストール
3.2 .devcontainer/docker-compose.yml
DevContainer用の差分だけ書きます。
services:
go:
volumes:
- ..:/workspaces:cached
# Go のデバッガ (dlv) に必要
cap_add:
- SYS_PTRACE
security_opt:
- seccomp:unconfined
# コンテナを常時起動状態にする
command: sleep infinity
3.3 docker-compose.yml(ルート)
backend/ 直下前提なので context: ./backend、working_dir: /app に統一しています。
services:
db:
image: postgres:15-alpine
restart: always
environment:
POSTGRES_USER: ${YOUR_USERNAME}
POSTGRES_PASSWORD: ${YOUR_PASSWORD}
POSTGRES_DB: ${YOUR_DBNAME}
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U YOUR_USERNAME -d YOUR_DBNAME"]
interval: 5s
timeout: 5s
retries: 5
api:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "8080:8080"
volumes:
- ./backend:/app
environment:
DB_DSN: ${DB_DSN}
depends_on:
db:
condition: service_healthy
# VS Code DevContainerがアタッチするサービス
# 同じDockerfileを使い回し、commandだけ上書きして常駐させる
go:
build:
context: ./backend
dockerfile: Dockerfile
entrypoint: []
working_dir: /app # backend/go.mod がある場所
volumes:
- ./backend:/app
depends_on:
- db
environment:
DB_DSN: ${DB_DSN}
command: sleep infinity
volumes:
postgres_data:
3.4 backend/Dockerfile(api + go 共用)
FROM golang:1.26-alpine
WORKDIR /app
# build tools + Chromium for headless scraping + 日本語フォント
RUN apk add --no-cache git build-base chromium \
&& apk add --no-cache \
font-noto-cjk \
font-noto-emoji \
font-freefont \
harfbuzz \
nss \
freetype \
fontconfig \
dbus \
ca-certificates \
curl
# ホットリロード用 Air
RUN go install github.com/air-verse/air@v1.61.1
# migrate CLI
RUN curl -fsSL "https://github.com/golang-migrate/migrate/releases/download/v4.17.0/migrate.linux-amd64.tar.gz" | tar xz \
&& mv migrate /usr/local/bin/migrate
COPY go.mod go.sum ./
ENV GOPROXY=direct
RUN go mod download
COPY . .
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENV CHROME_PATH=/usr/bin/chromium-browser
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["air", "-c", ".air.toml"]
goサービスではentrypoint: []+command: sleep infinityでENTRYPOINT/CMDを無効化しているので、同じイメージで「実行用」と「開発常駐用」を兼ねられます。
3.5 backend/docker-entrypoint.sh
#!/bin/sh
set -e
if [ -d "/app/db/migrations" ]; then
MAX_RETRIES=30
RETRY_COUNT=0
until migrate -path /app/db/migrations -database "$DB_DSN" up; do
RETRY_COUNT=$((RETRY_COUNT + 1))
if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then
echo "❌ Migrations failed after $MAX_RETRIES attempts."
exit 1
fi
echo "⏳ DB not ready yet. Retrying migrations ($RETRY_COUNT/$MAX_RETRIES)..."
sleep 2
done
echo "✅ Migrations completed."
else
echo "⚠️ Migration directory not found. Skipping."
fi
exec "$@"
depends_on: service_healthy+ リトライループの二重保険でDB起動待ち問題を回避。
backend/clientが無い分、パスは/app/db/migrationsに統一。
3.6 backend/.air.toml
root = "."
tmp_dir = "tmp"
[build]
cmd = "go build -o ./tmp/main ."
bin = "./tmp/main"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor"]
include_ext = ["go", "tpl", "tmpl", "html"]
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
log = "build-errors.log"
send_interrupt = false
stop_on_error = true
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
time = false
[misc]
clean_on_exit = true
3.7 backend/go.mod(例)
module example.com/myapp/backend
go 1.26
require (
github.com/PuerkitoBio/goquery v1.12.0
github.com/chromedp/chromedp v0.16.0
github.com/jackc/pgx/v5 v5.10.0
)
5. ハマりポイントと対策(backend直下版)
-
go.modが無いと言われる:working_dir: /app+volumes: ./backend:/appのセットを忘れるとbackend/の中身が見えない。 -
DB接続前にアプリが起動して落ちる:
healthcheck + condition: service_healthy+docker-entrypoint.shのリトライで解決。 -
Chromiumで日本語文字化け:
font-noto-cjk必須。CHROME_PATH=/usr/bin/chromium-browserを統一。 -
dlvが
operation not permitted:.devcontainer/docker-compose.ymlのSYS_PTRACE+seccomp:unconfinedが必要。