Docker & 컨테이너화 — Go 애플리케이션 배포
Go 애플리케이션은 단일 바이너리로 컴파일되어 컨테이너화에 이상적입니다. 최소한의 베이스 이미지(scratch, distroless)를 사용해 수 MB 수준의 이미지를 만들 수 있습니다.
기본 Dockerfile
# Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
# 의존성 캐싱 (코드 변경 시 재다운로드 방지)
COPY go.mod go.sum ./
RUN go mod download
# 소스 코드 복사 및 빌드
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w" \
-o server \
./cmd/server
# 최종 이미지 — scratch (빈 이미지)
FROM scratch
# SSL 인증서 (HTTPS 요청 시 필요)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 타임존 데이터 (필요시)
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# 바이너리만 복사
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
빌드 & 실행
# 이미지 빌드
docker build -t myapp:latest .
# 빌드 결과 확인 (scratch 이미지는 수 MB)
docker images myapp
# 실행
docker run -p 8080:8080 \
-e DATABASE_URL="postgres://..." \
-e REDIS_URL="redis://..." \
myapp:latest
멀티 스테이지 최적화 Dockerfile
# Dockerfile.production
FROM golang:1.22-alpine AS builder
# 빌드 도구 설치
RUN apk add --no-cache git make
WORKDIR /app
# 모듈 캐싱 레이어
COPY go.mod go.sum ./
RUN go mod download && go mod verify
# 전체 소스 복사
COPY . .
# 버전 정보 주입
ARG VERSION=unknown
ARG BUILD_TIME=unknown
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w \
-X main.version=${VERSION} \
-X main.buildTime=${BUILD_TIME}" \
-trimpath \
-o /bin/server \
./cmd/server
# 헬스 체크용 curl 대신 wget 사용
RUN go build -o /bin/healthcheck ./cmd/healthcheck
# ---- 최종 이미지 ----
# distroless: scratch보다 약간 크지만 디버깅 도구 포함
FROM gcr.io/distroless/static-debian12
COPY --from=builder /bin/server /server
COPY --from=builder /bin/healthcheck /healthcheck
# 비루트 사용자로 실행 (보안)
USER nonroot:nonroot
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/healthcheck"]
ENTRYPOINT ["/server"]
docker-compose — 로컬 개발 환경
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://user:password@postgres:5432/mydb?sslmode=disable
- REDIS_URL=redis://redis:6379
- APP_ENV=development
volumes:
- .:/app # 핫 리로드용 볼륨 마운트
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d # 초기화 SQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 5s
timeout: 5s
retries: 5
networks:
- app-network
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-network
# 개발용 DB 관리 툴
adminer:
image: adminer
ports:
- "8081:8080"
networks:
- app-network
volumes:
postgres-data:
redis-data:
networks:
app-network:
driver: bridge
개발용 Dockerfile (핫 리로드)
# Dockerfile.dev
FROM golang:1.22-alpine
RUN go install github.com/air-verse/air@latest
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
EXPOSE 8080
CMD ["air", "-c", ".air.toml"]
# .air.toml — air 설정
root = "."
tmp_dir = "tmp"
[build]
cmd = "go build -o ./tmp/server ./cmd/server"
bin = "./tmp/server"
include_ext = ["go"]
exclude_dir = ["vendor", "tmp"]
delay = 1000
[log]
time = true
# 개발 환경 시작
docker compose up
# 백그라운드 실행
docker compose up -d
# 로그 확인
docker compose logs -f app
# 특정 서비스만 재빌드
docker compose up --build app
# 전체 종료 및 볼륨 삭제
docker compose down -v
멀티서비스 docker-compose
# docker-compose.microservices.yml
version: '3.8'
services:
user-service:
build: ./services/user
ports: ["8081:8080"]
environment:
- DATABASE_URL=postgres://user:pass@postgres:5432/users
depends_on: [postgres]
order-service:
build: ./services/order
ports: ["8082:8080"]
environment:
- DATABASE_URL=postgres://user:pass@postgres:5432/orders
- USER_SERVICE_URL=http://user-service:8080
depends_on: [postgres, user-service]
api-gateway:
build: ./services/gateway
ports: ["8080:8080"]
environment:
- USER_SERVICE_URL=http://user-service:8080
- ORDER_SERVICE_URL=http://order-service:8080
depends_on: [user-service, order-service]
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: pass
volumes:
- ./init-db.sql:/docker-entrypoint-initdb.d/init.sql
kafka:
image: confluentinc/cp-kafka:latest
ports: ["9092:9092"]
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
.dockerignore
# .dockerignore
.git
.github
.gitignore
*.md
Makefile
docker-compose*.yml
**/*_test.go
**/testdata/
tmp/
dist/
coverage.out
Makefile Docker 타겟
IMAGE_NAME = myapp
VERSION = $(shell git describe --tags --always)
BUILD_TIME = $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
.PHONY: docker-build docker-push docker-run
docker-build:
docker build \
--build-arg VERSION=$(VERSION) \
--build-arg BUILD_TIME=$(BUILD_TIME) \
-t $(IMAGE_NAME):$(VERSION) \
-t $(IMAGE_NAME):latest \
.
docker-push:
docker push $(IMAGE_NAME):$(VERSION)
docker push $(IMAGE_NAME):latest
docker-run:
docker run --rm -p 8080:8080 $(IMAGE_NAME):latest
# 개발 환경
dev-up:
docker compose up -d
dev-down:
docker compose down
dev-logs:
docker compose logs -f
핵심 정리
| 기법 | 효과 |
|---|---|
| 멀티스테이지 빌드 | 최종 이미지에 빌드 도구 제외 |
scratch 베이스 | 5~15MB 이미지 |
distroless 베이스 | scratch + TLS 인증서 |
CGO_ENABLED=0 | 순수 Go 바이너리 (glibc 의존성 없음) |
-s -w ldflags | 디버그 심볼 제거 (~30% 크기 감소) |
USER nonroot | 루트 권한 없이 실행 (보안) |
| HEALTHCHECK | 컨테이너 상태 자동 모니터링 |