Docker 실행 환경과 개발 도구 통합: GitHub, Python, 그리고 실전 워크플로우 | 가나투데이
Docker 실행 환경과 개발 도구 통합: GitHub, Python, 그리고 실전 워크플로우
Docker가 실행되는 환경 이해하기
Docker를 제대로 활용하려면 먼저 Docker가 어떤 환경에서 실행되는지 이해해야 합니다.
Docker의 아키텍처
Docker는 크게 세 가지 구성 요소로 이루어져 있습니다:
1. Docker Client (명령줄 도구)
- 우리가
docker run,docker build같은 명령어를 입력하는 인터페이스 - 실제 작업은 Docker Daemon에게 요청합니다
2. Docker Daemon (dockerd)
- 백그라운드에서 실행되며 실제로 컨테이너를 관리하는 서버
- 이미지 빌드, 컨테이너 실행, 네트워크 관리 등을 담당
3. Docker Registry (Docker Hub)
- 이미지를 저장하고 공유하는 저장소
- GitHub가 코드 저장소라면, Docker Hub는 이미지 저장소
운영체제별 실행 환경
Linux에서의 Docker
Linux는 Docker의 본고장입니다. Docker는 Linux 커널의 기능을 직접 사용하므로 가장 효율적으로 실행됩니다.
# Docker가 사용하는 Linux 커널 기능 확인
docker info | grep "Kernel Version"
docker info | grep "Operating System"
# 시스템 리소스 확인
docker system df
free -h # 메모리 확인
df -h # 디스크 확인
Linux의 장점:
- 네이티브 성능 (가상화 오버헤드 없음)
- 서버 환경에 최적화
- 리소스 효율성 극대화
Windows에서의 Docker
Windows는 두 가지 방식으로 Docker를 실행합니다:
Windows 10/11 Pro 이상: WSL 2 (Windows Subsystem for Linux 2)
# WSL 2 상태 확인
wsl --list --verbose
# Docker Desktop 설정 확인
docker context ls
docker version
Docker Desktop은 WSL 2 내부에서 경량 Linux VM을 실행하여 Docker를 구동합니다.
Windows Home: Hyper-V 기반
- 가상화 기술을 사용하여 Linux 환경 제공
- 약간의 성능 오버헤드 존재
macOS에서의 Docker
macOS는 Linux 커널을 사용하지 않으므로, Docker Desktop이 경량 가상 머신을 실행합니다.
# Docker 리소스 할당 확인
docker info | grep "CPUs"
docker info | grep "Total Memory"
# macOS에서 사용 중인 하이퍼바이저 확인
docker context ls
Docker Desktop 설정에서 CPU, 메모리, 디스크 할당량을 조정할 수 있습니다.
Docker 실행 환경 최적화
# Docker 데몬 설정 확인
docker info
# 리소스 사용량 실시간 모니터링
docker stats
# 특정 컨테이너에 리소스 제한 설정
docker run -d \
--memory="512m" \
--cpus="1.5" \
--name limited-container \
nginx
GitHub와 Docker 통합: 현대적 개발 워크플로우
GitHub와 Docker를 함께 사용하면 코드와 실행 환경을 동시에 버전 관리할 수 있습니다.
프로젝트 구조 설계
my-project/
├── .github/
│ └── workflows/
│ └── docker-build.yml # GitHub Actions
├── src/
│ └── app.py
├── tests/
│ └── test_app.py
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
├── .gitignore
├── requirements.txt
└── README.md
.gitignore와 .dockerignore 설정
.gitignore (Git이 무시할 파일):
# Python
__pycache__/
*.py[cod]
*.so
.Python
env/
venv/
*.egg-info/
# Docker
.dockerignore
# IDE
.vscode/
.idea/
# 환경 변수
.env
.env.local
# 빌드 결과
dist/
build/
.dockerignore (Docker 이미지에 포함하지 않을 파일):
.git
.gitignore
.github
README.md
.env
.env.local
docker-compose.yml
*.md
tests/
.pytest_cache/
__pycache__/
*.pyc
.vscode/
GitHub Actions로 Docker 이미지 자동 빌드
프로젝트 루트에 .github/workflows/docker-build.yml 생성:
name: Docker Build and Push
on:
push:
branches: [ main, develop ]
tags:
- 'v*'
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 코드 체크아웃
uses: actions/checkout@v3
- name: Docker 메타데이터 설정
id: meta
uses: docker/metadata-action@v4
with:
images: |
yourusername/myapp
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Docker Hub 로그인
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Docker 이미지 빌드 및 푸시
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: 테스트 실행
run: |
docker run --rm yourusername/myapp:latest pytest
GitHub Secrets 설정:
- GitHub 저장소 → Settings → Secrets and variables → Actions
DOCKER_USERNAME과DOCKER_PASSWORD추가
GitHub Container Registry 사용
Docker Hub 대신 GitHub의 자체 컨테이너 레지스트리를 사용할 수도 있습니다:
- name: GitHub Container Registry 로그인
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 이미지 빌드 및 푸시
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
Python + Docker: 실전 개발 환경 구축
Python 프로젝트에서 Docker를 활용하는 실전 예제를 살펴보겠습니다.
1. 기본 Flask API 프로젝트
프로젝트 구조:
flask-api/
├── app/
│ ├── __init__.py
│ ├── main.py
│ └── models.py
├── tests/
│ └── test_api.py
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env.example
app/main.py:
from flask import Flask, jsonify, request
import os
from datetime import datetime
app = Flask(__name__)
# 환경 변수에서 설정 읽기
app.config['ENV'] = os.getenv('FLASK_ENV', 'production')
app.config['DEBUG'] = os.getenv('FLASK_DEBUG', 'False') == 'True'
@app.route('/')
def home():
return jsonify({
'message': 'Flask API with Docker',
'environment': app.config['ENV'],
'timestamp': datetime.now().isoformat()
})
@app.route('/api/data', methods=['GET', 'POST'])
def handle_data():
if request.method == 'POST':
data = request.get_json()
return jsonify({
'status': 'success',
'received': data
}), 201
return jsonify({
'data': [
{'id': 1, 'name': 'Item 1'},
{'id': 2, 'name': 'Item 2'}
]
})
@app.route('/health')
def health_check():
return jsonify({'status': 'healthy'}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
requirements.txt:
Flask==3.0.0
python-dotenv==1.0.0
gunicorn==21.2.0
pytest==7.4.3
requests==2.31.0
Dockerfile (멀티 스테이지 빌드):
# 베이스 이미지
FROM python:3.11-slim as base
# 환경 변수 설정
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# 작업 디렉토리
WORKDIR /app
# 의존성 빌드 스테이지
FROM base as builder
# 의존성 설치
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# 최종 실행 스테이지
FROM base as final
# 빌더에서 설치한 패키지 복사
COPY --from=builder /root/.local /root/.local
# PATH 업데이트
ENV PATH=/root/.local/bin:$PATH
# 애플리케이션 코드 복사
COPY ./app /app
# 포트 노출
EXPOSE 5000
# 헬스체크 추가
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:5000/health')"
# 운영 환경용 Gunicorn 사용
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--timeout", "60", "main:app"]
docker-compose.yml (개발 환경):
version: '3.8'
services:
web:
build:
context: .
target: base # 개발 시에는 base 스테이지만 사용
command: python main.py # 개발 모드
volumes:
- ./app:/app # 코드 변경 시 자동 반영
ports:
- "5000:5000"
environment:
- FLASK_ENV=development
- FLASK_DEBUG=True
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
depends_on:
- db
- redis
networks:
- app-network
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- app-network
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
adminer:
image: adminer
ports:
- "8080:8080"
networks:
- app-network
volumes:
postgres-data:
redis-data:
networks:
app-network:
driver: bridge
docker-compose.prod.yml (운영 환경):
version: '3.8'
services:
web:
build:
context: .
target: final # 최종 운영 이미지
command: gunicorn --bind 0.0.0.0:5000 --workers 4 main:app
restart: always
environment:
- FLASK_ENV=production
- FLASK_DEBUG=False
# 볼륨 마운트 제거 (운영 환경)
2. 실행 명령어 모음
# 개발 환경 시작
docker compose up -d
# 로그 확인 (실시간)
docker compose logs -f web
# 특정 서비스만 재시작
docker compose restart web
# 컨테이너 내부에서 명령 실행
docker compose exec web python
docker compose exec web flask shell
docker compose exec db psql -U postgres -d myapp
# 테스트 실행
docker compose exec web pytest
# 데이터베이스 마이그레이션
docker compose exec web flask db upgrade
# 운영 환경 실행
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 모든 서비스 중지 및 삭제
docker compose down
# 볼륨까지 삭제 (주의!)
docker compose down -v
3. Python 데이터 과학 프로젝트 with Docker
Dockerfile (Jupyter + Pandas + Scikit-learn):
FROM python:3.11-slim
# 시스템 패키지 설치
RUN apt-get update && apt-get install -y \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
# Python 패키지 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Jupyter 설정
RUN jupyter notebook --generate-config && \
echo "c.NotebookApp.ip = '0.0.0.0'" >> ~/.jupyter/jupyter_notebook_config.py && \
echo "c.NotebookApp.allow_root = True" >> ~/.jupyter/jupyter_notebook_config.py && \
echo "c.NotebookApp.token = ''" >> ~/.jupyter/jupyter_notebook_config.py
EXPOSE 8888
CMD ["jupyter", "notebook", "--no-browser", "--port=8888"]
requirements.txt:
jupyter==1.0.0
pandas==2.1.3
numpy==1.26.2
matplotlib==3.8.2
seaborn==0.13.0
scikit-learn==1.3.2
plotly==5.18.0
docker-compose.yml:
version: '3.8'
services:
jupyter:
build: .
ports:
- "8888:8888"
volumes:
- ./notebooks:/workspace/notebooks
- ./data:/workspace/data
- ./models:/workspace/models
environment:
- JUPYTER_ENABLE_LAB=yes
# 실행
docker compose up -d
# 브라우저에서 http://localhost:8888 접속
4. Django 프로젝트 with Docker
Dockerfile:
FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# 시스템 의존성
RUN apt-get update && apt-get install -y \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Python 의존성
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# 정적 파일 수집을 위한 디렉토리
RUN mkdir -p /app/staticfiles
# 엔트리포인트 스크립트
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
entrypoint.sh:
#!/bin/bash
echo "Waiting for postgres..."
while ! nc -z db 5432; do
sleep 0.1
done
echo "PostgreSQL started"
# 마이그레이션 실행
python manage.py migrate --noinput
# 정적 파일 수집
python manage.py collectstatic --noinput
exec "$@"
docker-compose.yml:
version: '3.8'
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/app
ports:
- "8000:8000"
environment:
- DEBUG=1
- SECRET_KEY=dev-secret-key-change-in-production
- DATABASE_URL=postgresql://postgres:password@db:5432/django_db
- ALLOWED_HOSTS=localhost,127.0.0.1
depends_on:
- db
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=django_db
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./staticfiles:/staticfiles
ports:
- "80:80"
depends_on:
- web
volumes:
postgres_data:
GitHub + Docker + Python 완전 통합 워크플로우
실전 시나리오: CI/CD 파이프라인 구축
1. 로컬 개발
# 저장소 클론
git clone https://github.com/username/my-python-app.git
cd my-python-app
# 개발 환경 시작
docker compose up -d
# 코드 수정 후 테스트
docker compose exec web pytest
# Git 커밋
git add .
git commit -m "Add new feature"
git push origin feature/new-feature
2. Pull Request 생성 시 자동 테스트
.github/workflows/test.yml:
name: Test
on:
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Docker Compose로 테스트 환경 구성
run: |
docker compose -f docker-compose.yml -f docker-compose.test.yml up -d
docker compose exec -T web pytest --cov=app --cov-report=xml
- name: 코드 커버리지 업로드
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
- name: 정리
run: docker compose down -v
3. Main 브랜치 머지 시 자동 배포
.github/workflows/deploy.yml:
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Docker 이미지 빌드
run: docker build -t myapp:${{ github.sha }} .
- name: Docker Hub에 푸시
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker tag myapp:${{ github.sha }} username/myapp:latest
docker push username/myapp:latest
- name: 서버에 배포 (SSH)
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/myapp
docker compose pull
docker compose up -d
docker system prune -f
개발 생산성을 높이는 Docker 팁
1. Makefile로 명령어 단순화
Makefile:
.PHONY: build up down logs shell test migrate
build:
docker compose build
up:
docker compose up -d
down:
docker compose down
logs:
docker compose logs -f
shell:
docker compose exec web python manage.py shell
bash:
docker compose exec web bash
test:
docker compose exec web pytest
migrate:
docker compose exec web python manage.py migrate
collectstatic:
docker compose exec web python manage.py collectstatic --noinput
createsuperuser:
docker compose exec web python manage.py createsuperuser
사용법:
make build # 이미지 빌드
make up # 서비스 시작
make logs # 로그 확인
make test # 테스트 실행
make shell # Django shell 접속
2. 개발용 VS Code DevContainer 설정
.devcontainer/devcontainer.json:
{
"name": "Python Development",
"dockerComposeFile": "../docker-compose.yml",
"service": "web",
"workspaceFolder": "/app",
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-azuretools.vscode-docker"
],
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.provider": "black"
}
}
VS Code에서 "Reopen in Container"를 선택하면 컨테이너 내부에서 직접 개발할 수 있습니다.
3. 핫 리로드 설정
Python 코드가 변경되면 자동으로 재시작되도록 설정:
services:
web:
build: .
command: >
sh -c "pip install watchdog &&
watchmedo auto-restart --directory=./ --pattern=*.py --recursive --
python main.py"
volumes:
- ./app:/app
문제 해결 가이드
문제 1: 컨테이너가 데이터베이스에 연결되지 않음
증상:
sqlalchemy.exc.OperationalError: could not connect to server
해결:
services:
web:
depends_on:
db:
condition: service_healthy # 데이터베이스가 준비될 때까지 대기
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
문제 2: 코드 변경이 반영되지 않음
원인: 볼륨 마운트 문제
해결:
# 컨테이너 재시작
docker compose restart web
# 이미지 재빌드 (캐시 무시)
docker compose build --no-cache web
docker compose up -d
문제 3: 포트 충돌
증상:
Error: port is already allocated
해결:
# 다른 포트 사용
docker compose up -d --scale web=0
docker run -p 5001:5000 myapp
# 또는 docker-compose.yml에서 포트 변경
ports:
- "5001:5000"
결론
Docker와 GitHub, Python을 함께 사용하면 다음과 같은 이점을 얻을 수 있습니다:
- 재현 가능한 개발 환경: 팀원 모두가 동일한 환경에서 작업
- 자동화된 CI/CD: 코드 푸시만으로 테스트와 배포 자동화
- 격리된 서비스 관리: 여러 프로젝트를 동시에 실행해도 충돌 없음
- 빠른 온보딩: 신규 개발자가
docker compose up한 번으로 시작 가능 - 운영 환경과의 일관성: 개발 환경이 곧 운영 환경
이제 Docker는 선택이 아닌 필수입니다. 위의 예제들을 기반으로 여러분의 프로젝트에 맞게 커스터마이징하여 사용해보시기 바랍니다.
#가나 투데이 #ganatoday
그린아프로




