Docker Compose로 Nginx + Flask + MySQL

Nginx + Flask + MySQL

이 글에서는 하나의 서버에서

  • Nginx(프록시/정적 파일)
  • Flask(Python 웹 애플리케이션)
  • MySQL(데이터베이스)

이 세 가지를 모두 도커 컨테이너로 구성하고,
이를 Docker Compose로 한 번에 띄우는 방법을 단계별로 정리한다.

최종 목표는 다음과 같다.

  • http://localhost:8080 으로 접속하면 Nginx → Flask → MySQL 까지 연동된 페이지가 뜬다.
  • 모든 서비스는 개별 컨테이너이지만, Docker Compose 네트워크로 자연스럽게 연결된다.
  • 코드 수정은 로컬에서 하고, 실행은 컨테이너에서 한다.

전체 구조 설계

먼저 디렉터리 구조부터 잡는다.

project-root/
├── docker-compose.yml
├── nginx/
│   └── default.conf
└── app/
    ├── Dockerfile
    ├── app.py
    └── requirements.txt

각 파일은 다음 역할을 한다.

  • docker-compose.yml
    → 전체 서비스를 정의하는 Docker Compose 설정 파일
  • nginx/default.conf
    → Nginx가 Flask 컨테이너로 요청을 프록시하는 설정
  • app/Dockerfile
    → Flask 애플리케이션용 커스텀 이미지 정의
  • app/app.py, app/requirements.txt
    → 실제 Flask 코드와 Python 의존성 목록

이렇게 나누어 두면, 이후 Docker Compose를 사용할 때 서비스별 역할이 명확해진다.


1. Flask 애플리케이션 작성

app/app.py:

from flask import Flask
import os
import pymysql

app = Flask(__name__)

DB_HOST = os.getenv("DB_HOST", "db")
DB_USER = os.getenv("DB_USER", "root")
DB_PASSWORD = os.getenv("DB_PASSWORD", "example")
DB_NAME = os.getenv("DB_NAME", "testdb")


def get_connection():
    return pymysql.connect(
        host=DB_HOST,
        user=DB_USER,
        password=DB_PASSWORD,
        database=DB_NAME,
        cursorclass=pymysql.cursors.DictCursor,
    )


@app.route("/")
def index():
    try:
        conn = get_connection()
        with conn.cursor() as cursor:
            cursor.execute("CREATE TABLE IF NOT EXISTS visitors (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));")
            cursor.execute("INSERT INTO visitors (name) VALUES ('Docker Compose User');")
            conn.commit()

            cursor.execute("SELECT COUNT(*) AS cnt FROM visitors;")
            result = cursor.fetchone()
            count = result["cnt"]

        conn.close()
        return f"<h1>Hello from Flask + MySQL!</h1><p>Total visitors: {count}</p>"
    except Exception as e:
        return f"<h1>Error</h1><pre>{e}</pre>"


if __name__ == "__main__":
    # 개발용 직접 실행 (도커에서는 gunicorn 또는 flask run 대신 이걸 써도 무방)
    app.run(host="0.0.0.0", port=5000)

이 코드는 다음을 수행한다.

  • 환경변수로 MySQL 접속 정보(DB_HOST, DB_USER 등)를 받는다.
  • 첫 요청 시 visitors 테이블을 생성하고, 방문자를 하나 추가한 뒤 현재 카운트를 보여준다.
  • DB 호스트를 db 로 지정했는데, 이 이름은 나중에 Docker Compose에서 MySQL 서비스 이름과 일치시키면 된다.

app/requirements.txt:

Flask==3.0.0
PyMySQL==1.1.0

이제 Flask 애플리케이션에 필요한 패키지를 명확하게 정의했으니,
Docker Compose로 이미지 빌드 시 이 파일을 활용할 수 있다.


2. Flask용 Dockerfile 작성

app/Dockerfile:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

이 Dockerfile은 다음 작업을 한다.

  • Python 3.11-slim 이미지를 기반으로 한다.
  • 작업 디렉터리를 /app 으로 설정한다.
  • requirements.txt를 복사하고, 필요한 패키지를 설치한다.
  • 나머지 애플리케이션 파일을 모두 복사한다.
  • 5000번 포트를 열고, 컨테이너 실행 시 python app.py를 실행한다.

이제 Docker Compose에서 이 Dockerfile을 이용해 Flask 이미지를 빌드하게 만들 것이다.


3. Nginx 설정 파일 작성

nginx/default.conf:

server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://flask:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

핵심 포인트:

  • Nginx는 80 포트에서 대기한다.
  • / 경로로 들어온 요청을 http://flask:5000 으로 보낸다.
  • 여기서 flask 는 Docker Compose에서 정의할 Flask 서비스 이름이다.
    Docker Compose를 사용하면 서비스 이름이 곧 호스트명이 된다.

이렇게 하면 Docker Compose 네트워크 안에서
Nginx 컨테이너가 Flask 컨테이너로 HTTP 요청을 프록시하게 된다.


4. Docker Compose 설정 작성

이제 모든 것을 묶어줄 docker-compose.yml 을 작성한다.

docker-compose.yml:

version: "3.8"

services:
  nginx:
    image: nginx:latest
    container_name: nginx
    ports:
      - "8080:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - flask

  flask:
    build: ./app
    container_name: flask
    environment:
      DB_HOST: db
      DB_USER: root
      DB_PASSWORD: example
      DB_NAME: testdb
    expose:
      - "5000"
    depends_on:
      - db

  db:
    image: mysql:8.0
    container_name: db
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: testdb
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

이 Docker Compose 설정을 하나씩 해석해보면 다음과 같다.

  • nginx 서비스
    • 공식 nginx:latest 이미지를 사용한다.
    • 호스트의 8080 포트를 컨테이너의 80 포트에 연결한다.
    • ./nginx/default.conf 파일을 Nginx 설정 경로에 read-only로 마운트한다.
    • depends_on 으로 flask 이후에 올라오도록 설정한다.
  • flask 서비스
    • ./app 디렉터리를 빌드 컨텍스트로 사용하여 Dockerfile을 빌드한다.
    • 환경변수로 DB 접속 정보를 전달한다(DB_HOST 등).
    • expose 로 내부 5000포트를 열어두고, Nginx가 이 포트로 접근할 수 있게 한다.
    • depends_on 으로 db 이후에 올라오도록 한다.
  • db 서비스
    • mysql:8.0 이미지를 사용한다.
    • 루트 패스워드와 초기 데이터베이스 이름을 환경변수로 전달한다.
    • db_data 라는 named volume을 /var/lib/mysql 에 마운트해서 데이터가 컨테이너 밖에 보존되도록 한다.
  • volumes
    • db_data 를 named volume으로 선언한다.
    • 컨테이너를 삭제해도 이 볼륨은 남기 때문에 MySQL 데이터가 유지된다.

이렇게 Docker Compose를 사용하면 세 개의 컨테이너를 한 번에 정의하고,
같은 네트워크에서 자연스럽게 통신하게 만들 수 있다.


5. Docker Compose로 한 번에 실행

루트 디렉터리(즉, docker-compose.yml 이 있는 곳)에서 다음을 실행한다.

docker-compose up -d

정상적으로 실행되면:

  • nginx, flask, db 세 컨테이너가 모두 올라온다.
  • Docker Compose는 자동으로 프로젝트용 네트워크를 하나 생성하고,
    세 컨테이너를 모두 그 네트워크에 붙인다.
  • nginx 컨테이너는 flask:5000 으로, flask 컨테이너는 db:3306 으로 접근할 수 있다.

상태 확인:

docker-compose ps

로그 확인:

docker-compose logs -f

6. 브라우저에서 연동 확인

이제 브라우저를 열고 다음 주소에 접속한다.

http://localhost:8080

정상적으로 구성되었다면:

  • Nginx 컨테이너가 요청을 받는다.
  • Nginx 설정에 따라 flask:5000 으로 프록시한다.
  • Flask 컨테이너가 MySQL 컨테이너 db:3306 에 접속해
    visitors 테이블을 만들고 데이터를 넣는다.
  • 총 방문자 수를 화면에 출력한다.

페이지를 여러 번 새로고침하면 Total visitors 숫자가 증가하는 것을 볼 수 있다.

이것이 바로 Docker Compose로 Nginx + Flask + MySQL 을 한 번에 연동한 결과다.


7. 수정과 재배포

코드를 수정하는 방식은 다음과 같다.

  • Flask 코드(app/app.py)를 수정한 경우
    → 이미지 기반이므로, 보통은 docker-compose build flaskdocker-compose up -d 를 다시 실행한다.
    → 개발 환경에서는 Dockerfile 대신 bind mount를 써서 app 디렉터리를 볼륨으로 연결하는 방법도 있다.
  • Nginx 설정(nginx/default.conf)을 바꾼 경우
    → 설정 파일은 볼륨으로 직접 연결되어 있으므로, 파일을 수정한 다음
    docker-compose restart nginx 정도로 재시작하면 적용된다.

Docker Compose를 사용하면
여러 컨테이너를 한 번에 내렸다 올릴 수 있기 때문에,
개발·테스트 환경을 관리하기가 훨씬 편해진다.


8. 마무리 정리

이 글에서 한 것은 다음과 같다.

  • Docker Compose로 세 개의 서비스를 정의했다.
    • Nginx: 외부 진입점, 리버스 프록시
    • Flask: 애플리케이션 서버
    • MySQL: 데이터베이스
  • Docker Compose 네트워크 덕분에
    • nginx → flaskflask:5000
    • flask → dbdb:3306 으로 이름 기반 통신이 가능해졌다.
  • Docker Compose의 volumes 기능으로 MySQL 데이터를 영구 저장했다.

이제 여기서 조금만 확장하면,

  • Docker Compose로 Redis 추가
  • Docker Compose로 여러 Flask 인스턴스와 로드밸런싱
    같은 고급 구성도 쉽게 시도할 수 있다.

원하면 이 다음에는

  • “개발 모드에서 Flask 코드를 bind mount로 바로 반영하는 Docker Compose 설정”
  • “Docker Compose에서 stage별(개발/운영) 설정 분리하는 방법”
    같은 주제로 이어서 더 깊게도 들어갈 수 있다.
    그 방향으로도 이어서 정리해줄까?