[도커 30강] 13강. compose로 Python + Redis 실습 환경 만들기
어제 12강에서 compose의 기본 구조를 익혔다면, 오늘은 한 단계 더 나아가서 Python 애플리케이션 + Redis를 실제로 묶어 실행하는 개발 환경을 만들어보겠습니다. 이 조합은 웹 서비스, 백그라운드 작업, 캐시 실습에서 가장 자주 등장하는 기본 패턴입니다. 핵심은 단순히 컨테이너를 띄우는 게 아니라, 팀원 누구나 같은 명령으로 같은 결과를 재현하고, 문제가 생겼을 때 빠르게 확인/복구할 수 있는 구조를 만드는 것입니다.
이번 강의에서는 Flask 기반의 작은 API를 예제로 사용합니다. API가 요청 횟수를 Redis에 저장하도록 구성해, 서비스 간 통신·환경변수·볼륨·로그 확인까지 한 번에 연습하겠습니다.
핵심 개념
- compose에서
app(Python)과redis(캐시)를 같은 프로젝트로 정의하면, 별도 IP 관리 없이 서비스 이름(redis)으로 안정적으로 통신할 수 있습니다. - 앱 컨테이너는 보통 소스 코드를 bind mount로 연결해 개발 생산성을 높이고, Redis 데이터는 named volume으로 분리해 컨테이너 재생성과 데이터 생명주기를 분리합니다.
depends_on은 시작 순서를 맞춰주지만 Redis 준비 완료(readiness)까지 보장하지는 않으므로, 애플리케이션 재시도 로직 또는 healthcheck 전략을 함께 가져가야 실무에서 안정적입니다.- Python 프로젝트를 컨테이너화할 때는
Dockerfile과compose.yaml의 책임을 분리하는 습관이 중요합니다. Dockerfile은 앱 이미지 설계, compose는 실행 환경 조립입니다.
기본 사용
예제 1) 프로젝트 뼈대와 compose 파일 만들기
먼저 최소 구조를 만듭니다. 아래 명령은 실습 디렉터리, Flask 앱, requirements, Dockerfile, compose 파일까지 한 번에 생성합니다.
mkdir -p ~/docker100/lesson13/app
cd ~/docker100/lesson13
cat > app/app.py <<'PY'
import os
import time
from flask import Flask
import redis
app = Flask(__name__)
redis_host = os.getenv("REDIS_HOST", "redis")
redis_port = int(os.getenv("REDIS_PORT", "6379"))
client = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)
@app.get("/")
def index():
for _ in range(10):
try:
count = client.incr("hits")
return {
"message": "hello from docker compose",
"redis": f"{redis_host}:{redis_port}",
"hits": count,
}
except redis.exceptions.ConnectionError:
time.sleep(0.5)
return {"error": "redis not ready"}, 503
PY
cat > app/requirements.txt <<'REQ'
flask==3.0.3
redis==5.0.7
REQ
cat > app/Dockerfile <<'DOCKER'
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["flask", "--app", "app", "run", "--host=0.0.0.0", "--port=8000"]
DOCKER
cat > compose.yaml <<'YAML'
services:
app:
build: ./app
container_name: lesson13_app
ports:
- "18013:8000"
environment:
FLASK_ENV: development
REDIS_HOST: redis
REDIS_PORT: "6379"
volumes:
- ./app:/app
depends_on:
- redis
redis:
image: redis:7-alpine
container_name: lesson13_redis
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis_data:/data
volumes:
redis_data:
YAML
설명:
app서비스는 Dockerfile로 직접 빌드해 Python 의존성을 고정합니다../app:/appbind mount 덕분에 코드 수정이 컨테이너에 즉시 반영됩니다(개발용).- Redis는
appendonly yes로 AOF를 켜서 데이터 내구성을 높였고,/data를 named volume으로 분리했습니다.
예제 2) 기동, 연결 확인, 로그 점검
이제 실제로 올리고, 앱과 Redis가 제대로 붙는지 확인합니다.
cd ~/docker100/lesson13
docker compose config
docker compose up -d --build
docker compose ps
curl -s http://localhost:18013 | jq .
curl -s http://localhost:18013 | jq .
docker compose logs --tail=80 app redis
설명:
docker compose config는 배포 전 문법/병합 결과를 확인하는 습관입니다.- 두 번 요청하면
hits값이 증가해야 Redis 카운터가 정상 동작하는 것입니다. logs를 앱/Redis 함께 보면 어느 쪽 문제인지 빠르게 분리할 수 있습니다.
예제 3) 데이터 지속성, 재시작, 내부 통신 검증
실무에서는 “컨테이너를 지웠을 때 데이터가 남는가?”를 꼭 확인해야 합니다.
cd ~/docker100/lesson13
# Redis 내부 값 확인
docker compose exec redis redis-cli GET hits
# 앱만 재생성 후 재확인
docker compose up -d --force-recreate app
curl -s http://localhost:18013 | jq .
# 전체 down/up 후에도 Redis 데이터가 유지되는지 확인
docker compose down
docker compose up -d
sleep 2
docker compose exec redis redis-cli GET hits
# 내부 DNS 통신 점검
docker compose exec app python -c "import redis; r=redis.Redis(host='redis',port=6379,decode_responses=True); print(r.ping())"
설명:
- 앱 컨테이너를 갈아끼워도 Redis 볼륨이 유지되면 카운터 값이 남습니다.
down후up에서도 named volume이 유지되는 것이 정상입니다(단,-v로 내리면 삭제됨).- 마지막 ping 테스트는 compose 내부 DNS(
redis)가 기대대로 동작하는지 확인하는 최소 진단입니다.
자주 하는 실수
실수 1) 앱에서 Redis 호스트를 localhost로 설정
- 원인: 컨테이너 내부에서의
localhost는 자기 자신 컨테이너라는 점을 잊음. - 해결: compose 네트워크에서는 서비스 이름(
redis)을 사용합니다. 즉REDIS_HOST=redis가 기본값이어야 합니다.
실수 2) depends_on만 믿고 재시도 로직 없이 바로 연결
- 원인: 컨테이너 시작과 서비스 준비 완료를 같은 개념으로 오해.
- 해결: 앱 코드에 짧은 재시도 루프를 넣고, 이후 고급 단계에서 healthcheck + 의존 조건을 조합합니다.
실수 3) 코드와 데이터를 모두 bind mount로 처리
- 원인: 편의성만 생각해
/data까지 호스트 경로에 묶음. - 해결: 소스는 bind mount, Redis/DB 데이터는 named volume으로 분리합니다. 백업/복구 전략도 이 기준에서 세우면 깔끔합니다.
실수 4) compose 재시작 습관 없이 문제를 코드 탓으로만 판단
- 원인: 캐시된 이미지/오래된 컨테이너 상태를 고려하지 않음.
- 해결: 문제가 애매하면
docker compose up -d --build, 필요 시--force-recreate로 상태를 초기화하고 다시 검증합니다.
실무 패턴
Python + Redis 조합을 팀에서 오래 운영하려면, “지금 돌아간다”보다 “내일도 누구나 복구 가능하다”에 초점을 맞춰야 합니다.
첫째, 환경 변수 계약을 문서화합니다. REDIS_HOST, REDIS_PORT, APP_ENV 같은 키를 README와 compose에서 동일하게 유지하면, 로컬/CI/스테이징 간 설정 드리프트를 크게 줄일 수 있습니다.
둘째, 장애 관측 포인트를 고정합니다. API 장애가 나면 보통 (1) 앱 예외, (2) Redis 연결 실패, (3) 네트워크/DNS 문제 순으로 보게 되는데, 이를 docker compose ps → logs → exec 점검 순서로 팀 표준화하면 신규 인원도 빠르게 대응합니다.
셋째, 개발용 compose와 운영용 compose를 분리할 준비를 합니다. 오늘 예제의 bind mount와 개발 친화 옵션은 로컬에는 좋지만 운영에는 불필요할 수 있습니다. 운영에서는 immutable 이미지, 명시적 리소스 제한, 비밀정보 분리(시크릿/환경 주입)로 바꿔야 합니다.
넷째, 재현 가능한 디버깅 스크립트를 남깁니다. 예를 들어 make up, make logs, make reset 같은 공용 명령을 제공하면 팀 커뮤니케이션 비용이 크게 줄고, “누구는 되고 누구는 안 되는” 상황을 줄일 수 있습니다.
다섯째, Redis를 캐시로만 볼지, 작업 큐/세션 저장까지 확장할지 초기에 결정합니다. 용도에 따라 만료 정책(TTL), 영속성(AOF/RDB), 모니터링 지표가 달라지므로, compose 단계부터 최소한의 운영 가정은 합의해두는 것이 좋습니다.
오늘의 결론
한 줄 요약: compose로 Python + Redis를 묶으면 개발 환경 재현성과 팀 협업 속도가 급격히 좋아지며, 핵심은 서비스명 기반 통신·데이터 분리·진단 루틴의 표준화다.
연습문제
- 오늘 예제를 그대로 실행한 뒤,
curl http://localhost:18013을 5회 호출하고hits증가를 확인하세요. 증가가 멈추면 어디부터 진단할지 순서를 적어보세요. REDIS_HOST를 일부러localhost로 바꿔 실패를 재현한 다음, 로그와 에러 메시지를 근거로 왜 실패했는지 설명해보세요.docker compose down -v를 실행한 뒤 다시 올려서hits값이 어떻게 달라지는지 확인하고, named volume 삭제가 어떤 의미인지 정리해보세요.
이전 강의 정답
12강 연습문제 해설:
web + redis2서비스 compose에서docker compose ps를 보면 각 서비스의 상태(Up/Exited)와 포트 매핑(예:0.0.0.0:8082->80/tcp)을 프로젝트 단위로 확인할 수 있습니다. 이 출력은 단순 실행 여부뿐 아니라 “외부 접근 가능한 서비스가 무엇인지”를 즉시 보여주는 운영 핵심 정보입니다.- 서비스명 기반 접근은 IP 하드코딩보다 유지보수에 훨씬 유리합니다. 컨테이너가 재생성되면 내부 IP는 바뀔 수 있지만 서비스 이름(
redis)은 유지되기 때문에, 애플리케이션 설정 변경 없이 안정적인 통신이 가능합니다. - named volume을 붙인 서비스는 컨테이너를 삭제/재생성해도 볼륨이 남아 데이터가 유지됩니다. 즉 컨테이너 생명주기와 데이터 생명주기를 분리한 것이며, 이것이 상태성 서비스에서 compose를 실무적으로 써야 하는 핵심 이유입니다.
실습 환경/재현 정보
- OS: macOS 15+ (Apple Silicon) 또는 Ubuntu 22.04+
- Docker 버전: Docker Engine/Desktop 25.x 이상
- Compose 버전: v2.x (
docker compose version) - Python: 3.12-slim 이미지 기반
- Redis:
redis:7-alpine+ AOF 사용 - 실행 순서:
~/docker100/lesson13구조 생성app.py,requirements.txt,Dockerfile,compose.yaml작성docker compose config로 설정 점검docker compose up -d --build로 실행curl,docker compose logs,redis-cli로 동작 검증docker compose down으로 정리(데이터 유지 확인 시-v없이)
- 재현 체크:
curl응답의hits가 호출마다 증가하는가?- 앱에서 Redis 연결 오류가 없는가?
- 컨테이너 재생성 후에도 Redis 데이터가 유지되는가?
docker compose exec app ... ping이 True를 반환하는가?- 팀원이 같은 compose 파일로 동일 결과를 재현하는가?