테마 전환

Docker Compose로 PHP 환경 한 번에 배포하기: DNMP 완전 가이드(Nginx+MySQL+PHP)

Easton editorial illustration: service mesh rail yard

터미널에 오류 메시지가 계속 올라옵니다. “Nginx가 PHP-FPM의 socket 파일을 찾지 못합니다.” 오늘 밤 들어 여덟 번째 오류입니다. LNMP 환경을 수동으로 설치하면서 nginx.conf, php-fpm.conf, my.cnf 세 설정 파일을 수정하고 서비스를 열 번 넘게 재시작했지만, 여전히 환경이 실행되지 않습니다.

옆자리 동료가 말합니다. “내 컴퓨터에서는 잘 되는데요. PHP 버전이 다른 것 아닌가요?”

동료는 PHP 7.4를 사용하고, 저는 8.1을 설치했습니다. 동료의 MySQL은 5.7인데 저는 실수로 8.0을 설치했습니다. 코드가 실행되지 않는 것도 당연했습니다.

10분
배포 시간
한 번에 배포 완료
100%
환경 일관성
팀 전체가 같은 환경 사용
낮음
설정 복잡도
docker-compose로 한 번에 시작

팀원마다 개발 환경이 다르고, 신입 구성원은 환경 설정에만 반나절이 걸립니다. 기존 구성원도 일대일로 안내해야 합니다. A 컴퓨터에서 잘 실행되던 코드가 B 컴퓨터에서는 오류를 냅니다. 서버 배포는 더더욱 운에 맡기는 일이 됩니다.

Docker Compose로 DNMP 환경을 한 번에 배포해 본 뒤에야 이런 문제가 완전히 사라졌습니다. 10분이면 Nginx, MySQL, PHP 전체 환경이 준비되고, 모든 팀원이 같은 설정 파일을 사용하니 더는 “내 컴퓨터에서는 되는데요”라는 말도 나오지 않습니다.

PHP 환경 배포에 Docker Compose를 선택하는 이유

기존 LNMP 배포의 세 가지 문제점

문제점 1: 수동 설치와 설정이 번거롭고, 한 단계가 틀리면 이후 단계까지 모두 꼬입니다

처음 LNMP를 설치했던 때를 기억하시나요? 먼저 apt-get install nginx를 실행하고, php-fpm을 설치한 다음 MySQL을 설치합니다. 설치가 끝나도 설정 파일을 수정해야 합니다. nginx.conf에는 location 규칙을 설정하고, php.ini에서는 확장 기능을 활성화하며, my.cnf의 매개변수도 조정해야 합니다.

세 설정 파일은 /etc/nginx/, /etc/php/, /etc/mysql/처럼 서로 다른 디렉터리에 흩어져 있습니다. 설정 하나만 잘못 작성해도 서비스가 시작되지 않습니다. 문제를 찾으려면 Nginx, PHP-FPM, MySQL 로그를 모두 뒤져야 하니 머리가 아플 수밖에 없습니다.

php-fpm.conf의 listen = 127.0.0.1:9000listen = /run/php/php7.4-fpm.sock으로 적는 바람에 Nginx가 PHP를 찾지 못해 세 시간 동안 문제를 추적한 경우도 봤습니다.

문제점 2: 버전 호환성 문제가 개발자를 지치게 합니다

팀에는 늘 특정 버전을 고집하는 사람이 몇 명씩 있습니다. 기존 프로젝트는 PHP 5.6에서 실행되는데 새 프로젝트는 PHP 8.1이 필요합니다. MySQL 5.7에서 동작하던 GROUP BY 구문이 MySQL 8.0에서는 오류를 낼 수 있습니다. 8.0에서는 ONLY_FULL_GROUP_BY 모드가 기본으로 활성화되기 때문입니다.

더 큰 문제는 구성원 컴퓨터마다 버전 조합이 제각각이라는 점입니다.

  • 이 씨의 Mac: PHP 7.4 + MySQL 5.7
  • 왕 씨의 Ubuntu: PHP 8.0 + MySQL 8.0
  • 새 인턴의 Windows: PHP 8.1 + MariaDB 10.6

이 씨의 컴퓨터에서 잘 실행되는 코드를 Git에 push했는데, 왕 씨가 pull하면 오류가 납니다. 테스트 환경은 또 다른 버전 조합이고, 운영 환경 버전도 다릅니다. 위험할 수밖에 없습니다.

문제점 3: 팀 협업 효율이 지나치게 낮습니다

신입 구성원이 입사한 첫날에는 기존 구성원이 반나절 동안 환경 설치를 하나하나 알려줘야 합니다.

  1. Nginx 설치(20분)
  2. PHP와 확장 기능 설치(30분, 컴파일도 필요)
  3. MySQL 설치(15분)
  4. 세 서비스가 서로 통신하도록 설정(1시간, 여러 문제 발생)
  5. 테스트 데이터 가져오기(10분)

운이 좋으면 오후부터 코드를 작성할 수 있습니다. 운이 나쁘면 다음 날로 넘어갑니다.

예전에 다니던 스타트업에서는 환경 차이 때문에 간단한 회원 가입 기능이 개발 환경 테스트는 통과했지만 배포 후 “데이터베이스 연결 실패” 오류를 냈습니다. 개발 환경의 MySQL root 계정에는 비밀번호가 없고 운영 환경에는 비밀번호가 있었는데, 설정 파일 수정을 잊은 것이 원인이었습니다. 새벽에 긴급 rollback을 했고, 대표에게 크게 혼날 뻔했습니다.

Docker Compose의 세 가지 장점

장점 1: 설정을 코드로 관리하며 하나의 파일을 팀 전체가 공유합니다

Docker Compose는 모든 설정을 하나의 docker-compose.yml 파일에 작성합니다.

  • 어떤 버전의 Nginx를 사용할 것인가?(nginx:1.25-alpine)
  • PHP에 어떤 확장 기능을 설치할 것인가?(mysqli, pdo_mysql, redis)
  • MySQL 설정 매개변수는 무엇인가?(my.cnf로 통합 관리)
  • 서비스끼리 어떻게 통신할 것인가?(자동 DNS 해석)

이 파일을 Git에 올리면 모든 팀원이 pull한 뒤 똑같은 환경을 사용합니다. 신입 구성원이 입사했나요? 프로젝트를 clone하고 docker-compose up -d 명령 하나만 실행하면 됩니다. 커피 한 잔 마시고 돌아오면 환경이 준비되어 있습니다.

이제 “MySQL 비밀번호가 뭔가요?”, “php.ini가 어디에 있나요?”, “Nginx 설정 좀 보여 주세요”라고 물을 필요가 없습니다. 파일 하나로 모든 문제를 해결할 수 있습니다.

장점 2: 한 번에 시작하고 제거할 수 있어 깔끔합니다

모든 서비스를 시작합니다.

docker-compose up -d

모든 컨테이너를 중지하고 삭제합니다.

docker-compose down

PHP 버전을 바꾸고 싶나요? image: php:7.4-fpmimage: php:8.1-fpm으로 한 줄만 수정한 뒤 다시 up하면 끝입니다.

기존 방식처럼 이전 PHP 버전을 제거하면서 잔여 파일까지 일일이 정리하거나, 잘못 지워 시스템이 망가질까 걱정할 필요가 없습니다. Docker 컨테이너는 서로 완전히 격리되어 있으므로 삭제하면 흔적 없이 깔끔하게 사라집니다.

장점 3: 환경을 격리하고 여러 버전을 함께 사용할 수 있습니다

PHP 7.4와 PHP 8.1을 동시에 실행하고 싶나요? 기존 방식이라면 PHP를 두 벌 설치하고 서로 다른 포트를 설정한 다음, Nginx의 fastcgi_pass가 각기 다른 곳을 가리키도록 수정해야 합니다. 매우 번거롭습니다.

Docker Compose에서는 설정 파일에 PHP 서비스 두 개를 정의하면 됩니다.

php74:
  image: php:7.4-fpm

php81:
  image: php:8.1-fpm

Nginx 설정에서 사용하려는 서비스 이름을 지정하면 됩니다. 기존 프로젝트는 php74를 계속 사용하고 새 프로젝트는 php81을 사용하므로 서로 간섭하지 않습니다.

GitHub에서 star가 가장 많은 DNMP 프로젝트인 imeepo/dnmp는 5,000개가 넘는 star를 받았습니다. 많은 팀이 이미 이 방식을 검증했다는 뜻입니다. 새로 등장한 실험적인 방식이 아니라 성숙하고 안정적인 해결책입니다.

DNMP 아키텍처 상세 설명

DNMP란 무엇인가요?

DNMP는 LNMP의 Docker 버전이며, 네 글자는 각각 하나의 구성 요소를 뜻합니다.

  • D = Docker: 각 서비스를 독립된 ‘컨테이너’에 넣는 컨테이너화 플랫폼
  • N = Nginx: HTTP 요청을 받아 PHP에 전달하는 Web 서버
  • M = MySQL: 데이터를 저장하는 관계형 데이터베이스(MariaDB나 PostgreSQL로 교체 가능)
  • P = PHP: PHP 코드를 실행하는 PHP-FPM 런타임 환경

이 네 가지 핵심 구성 요소 외에도 일반적으로 다음 서비스를 추가합니다.

  • Redis: 데이터 조회 속도를 높이는 캐시 서비스
  • PHPMyAdmin: MySQL을 시각적으로 조작할 수 있는 데이터베이스 관리 도구

기존 LNMP에서는 이 서비스들을 시스템에 직접 설치해 한곳에 섞어 놓습니다. DNMP에서는 각 서비스를 별도의 Docker 컨테이너에 설치합니다. 서로 다른 소프트웨어를 각각 다른 가상 머신에 설치하는 것과 비슷하지만, 가상 머신보다 훨씬 가볍습니다.

서비스 오케스트레이션 아키텍처

앰프, 스피커, 플레이어로 오디오 시스템을 조립한다고 생각해 보세요. 각 장치는 독립적으로 작동하지만, 함께 소리를 내려면 케이블로 연결해야 합니다.

DNMP도 같습니다. Nginx, PHP, MySQL은 서로 독립된 세 컨테이너지만 Docker 네트워크를 통해 연결해야 합니다.

요청 처리 과정:

  1. 브라우저가 localhost:80으로 HTTP 요청을 보냅니다.
  2. Nginx 컨테이너가 요청을 받고 PHP 파일임을 확인합니다.
  3. Nginx가 9000 포트를 통해 PHP 컨테이너로 요청을 전달합니다(서비스 이름 php 사용).
  4. PHP 컨테이너가 코드를 실행하고, 데이터베이스 조회가 필요하면 MySQL 컨테이너에 연결합니다(서비스 이름 mysql 사용).
  5. PHP가 결과를 Nginx에 반환합니다.
  6. Nginx가 HTML을 브라우저에 반환합니다.

통신 방식:
Docker Compose는 bridge network를 자동으로 생성하고 모든 서비스를 연결합니다. 서비스마다 고유한 서비스 이름이 있으며, Docker가 DNS 해석을 자동으로 처리합니다.

예를 들어 PHP에서 MySQL에 연결할 때 127.0.0.1:3306을 사용할 필요가 없습니다. mysql:3306이라고 작성하면 됩니다. Docker가 mysql을 MySQL 컨테이너의 IP 주소로 자동 변환해 줍니다. 매우 편리합니다.

포트 매핑:

  • Nginx 컨테이너: 80 포트 → 호스트의 80 포트(localhost에 접속하면 웹사이트 표시)
  • MySQL 컨테이너: 3306 포트 → 호스트의 3306 포트(Navicat 같은 도구로 연결 가능)
  • PHPMyAdmin 컨테이너: 80 포트 → 호스트의 8080 포트(localhost:8080에서 데이터베이스 관리)
  • Redis 컨테이너: 6379 포트 → 호스트의 6379 포트

볼륨 마운트(Volume):
컨테이너를 삭제하면 데이터도 사라질까요? 그렇지 않습니다. 볼륨 마운트를 사용해 컨테이너의 중요한 디렉터리를 호스트에 연결합니다.

  • ./www → Nginx와 PHP 컨테이너의 /var/www/html(코드 디렉터리)
  • ./mysql/data → MySQL 컨테이너의 /var/lib/mysql(데이터베이스 파일)
  • ./nginx/logs → Nginx 컨테이너의 /var/log/nginx(접속 로그)

컨테이너를 삭제하고 다시 생성해도 데이터는 호스트에 남아 있으므로 사라지지 않습니다.

디렉터리 구조 설계

표준 DNMP 프로젝트의 디렉터리 구조는 다음과 같습니다.

dnmp/
├── docker-compose.yml       # 핵심 오케스트레이션 파일, 모든 서비스 정의
├── .env                     # 환경 변수 설정(비밀번호, 포트 등)
├── .gitignore               # Git ignore 파일(.env는 업로드하지 않음)
├── nginx/
│   ├── conf.d/
│   │   └── default.conf    # 사이트 설정(root 디렉터리, PHP 전달 규칙)
│   └── logs/               # 접속 로그와 오류 로그
│       ├── access.log
│       └── error.log
├── php/
│   ├── Dockerfile          # PHP 이미지 사용자 정의(확장 기능 설치)
│   ├── php.ini             # PHP 설정(메모리 제한, 업로드 크기)
│   └── php-fpm.conf        # PHP-FPM 설정(프로세스 수)
├── mysql/
│   ├── data/               # 데이터베이스 파일 영속성 디렉터리
│   └── my.cnf              # MySQL 설정(문자 집합, 최대 연결 수)
└── www/                    # 프로젝트 코드 디렉터리
    └── index.php           # 테스트 파일

핵심 파일 설명:

  • docker-compose.yml: 사용할 이미지, 마운트할 디렉터리, 매핑할 포트를 정의하는 가장 중요한 파일
  • .env: MYSQL_ROOT_PASSWORD=123456 같은 환경 변수를 저장하고 민감한 정보를 별도로 관리하는 파일
  • nginx/conf.d/default.conf: 웹사이트 root 디렉터리와 PHP 전달 규칙을 설정하는 Nginx 사이트 설정 파일
  • php/Dockerfile: 공식 PHP 이미지를 기반으로 mysqli, redis 같은 확장 기능을 추가 설치하는 파일
  • mysql/data/: 데이터베이스 파일을 저장하는 위치로, 컨테이너를 삭제하고 다시 생성해도 데이터 유지

파일 수가 많아 보일 수 있지만, 각 파일의 역할이 명확합니다. 기존 LNMP처럼 설정이 여러 위치에 흩어져 찾기 어려운 구조가 아닙니다.

10분 실습: 처음부터 DNMP 환경 구축하기

사전 준비

1. Docker 설치

  • Mac/Windows: Docker Desktop을 다운로드해 설치하면 됩니다. docker-compose도 함께 제공됩니다.
  • Linux(Ubuntu 예시):
    sudo apt update
    sudo apt install docker.io docker-compose -y
    sudo systemctl start docker
    sudo systemctl enable docker

2. 설치 확인

docker --version
# 출력: Docker version 24.0.6, build xxx

docker-compose --version
# 출력: Docker Compose version v2.21.0

버전 번호가 표시되면 설치가 완료된 것입니다.

3. 권장 사양

  • 메모리: 최소 4GB(Docker Desktop 설정에서 조정 가능)
  • 디스크: 최소 20GB의 여유 공간(이미지가 일부 공간을 사용)
  • 네트워크: Docker Hub에 접속 가능해야 함(네트워크가 느리면 가까운 이미지 mirror 설정 가능)

빠른 배포 방법 1: 검증된 오픈 소스 프로젝트 사용(권장)

직접 설정하기 번거롭다면 오픈 소스 프로젝트를 사용해 10분 만에 끝낼 수 있습니다.

1. 프로젝트 clone

Arm CPU(Apple M 시리즈 칩 포함)를 지원하는 imeepo/dnmp를 추천합니다.

git clone https://github.com/imeepo/dnmp.git
cd dnmp

2. 환경 변수 설정

예제 설정 파일을 복사합니다.

cp .env.example .env

.env 파일을 열고 핵심 설정 몇 가지를 수정합니다.

# MySQL root 비밀번호(123456처럼 약한 비밀번호는 사용하지 마세요)
MYSQL_ROOT_PASSWORD=your_strong_password

# 시간대 설정
TZ=Asia/Shanghai

# 포트 매핑(80 포트가 사용 중이면 8080으로 변경)
NGINX_HTTP_PORT=80
MYSQL_PORT=3306

3. 한 번에 시작

docker-compose up -d

-d 매개변수는 백그라운드 실행을 뜻합니다. 처음 시작할 때 이미지를 가져오므로 몇 분 걸릴 수 있습니다. 다음과 같은 출력이 보이면 성공한 것입니다.

Creating network "dnmp_default" with the default driver
Creating dnmp_mysql_1 ... done
Creating dnmp_php_1   ... done
Creating dnmp_nginx_1 ... done
Creating dnmp_redis_1 ... done

4. 설치 확인

브라우저에서 http://localhost에 접속합니다. PHP 버전과 설치된 확장 기능 등을 보여 주는 phpinfo 페이지가 표시되어야 합니다.

이 페이지가 보이면 Nginx와 PHP가 모두 실행되고 있는 것입니다.

이어서 http://localhost:8080에 접속하면 PHPMyAdmin 로그인 화면이 표시되어야 합니다.

  • 서버: mysql(localhost가 아님)
  • 사용자 이름: root
  • 비밀번호: .env에서 설정한 비밀번호

로그인에 성공하면 MySQL도 정상적으로 작동하는 것입니다.

5. 컨테이너 상태 확인

docker-compose ps

출력은 다음과 비슷해야 합니다.

Name               Command              State           Ports
--------------------------------------------------------------------
dnmp_nginx_1   nginx -g daemon off;   Up      0.0.0.0:80->80/tcp
dnmp_php_1     php-fpm                Up      9000/tcp
dnmp_mysql_1   docker-entrypoint...   Up      0.0.0.0:3306->3306/tcp
dnmp_redis_1   redis-server           Up      6379/tcp

StateUp이면 컨테이너가 실행 중입니다.

사용자 정의 방법 2: docker-compose.yml 직접 작성(고급)

원리를 깊이 이해하고 싶다면 설정 파일을 직접 작성해 보세요.

1. 프로젝트 디렉터리 생성

mkdir my-dnmp && cd my-dnmp
mkdir -p nginx/conf.d php mysql/data www

2. docker-compose.yml 작성

docker-compose.yml 파일을 만들고 다음 내용을 입력합니다.

version: '3.8'

services:
  # Nginx 서비스
  nginx:
    image: nginx:1.25-alpine  # 더 작은 alpine 버전 이미지 사용
    container_name: dnmp-nginx
    ports:
      - "80:80"  # 80 포트를 호스트에 매핑
    volumes:
      - ./www:/var/www/html  # 코드 디렉터리
      - ./nginx/conf.d:/etc/nginx/conf.d  # 사이트 설정
      - ./nginx/logs:/var/log/nginx  # 로그 디렉터리
    depends_on:
      - php  # PHP 서비스에 의존하므로 PHP를 먼저 시작한 뒤 Nginx 시작
    networks:
      - dnmp-network

  # PHP 서비스
  php:
    build: ./php  # Dockerfile로 이미지 빌드
    container_name: dnmp-php
    volumes:
      - ./www:/var/www/html  # 코드 디렉터리(Nginx와 동일하게 유지)
    networks:
      - dnmp-network

  # MySQL 서비스
  mysql:
    image: mysql:8.0
    container_name: dnmp-mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root123456  # root 비밀번호
      MYSQL_DATABASE: test_db  # 기본 생성 데이터베이스
      TZ: Asia/Shanghai  # 시간대
    volumes:
      - ./mysql/data:/var/lib/mysql  # 데이터 영속성
    networks:
      - dnmp-network

  # Redis 서비스(선택 사항)
  redis:
    image: redis:7-alpine
    container_name: dnmp-redis
    ports:
      - "6379:6379"
    networks:
      - dnmp-network

networks:
  dnmp-network:
    driver: bridge  # 컨테이너끼리 통신할 수 있는 bridge network

3. PHP Dockerfile 생성

php/Dockerfile을 생성하고 자주 사용하는 확장 기능을 설치합니다.

FROM php:8.1-fpm

# 시스템 의존성 설치
RUN apt-get update && apt-get install -y \
    libzip-dev \
    zip \
    unzip

# PHP 확장 기능 설치
RUN docker-php-ext-install \
    mysqli \
    pdo_mysql \
    zip \
    opcache

# Redis 확장 기능 설치
RUN pecl install redis && docker-php-ext-enable redis

# 작업 디렉터리 설정
WORKDIR /var/www/html

4. Nginx 사이트 설정 생성

nginx/conf.d/default.conf를 생성합니다.

server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

    # 접속 로그
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # PHP 파일을 PHP-FPM으로 전달해 처리
    location ~ \.php$ {
        fastcgi_pass php:9000;  # php는 서비스 이름이며 Docker가 IP를 자동 해석
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # 정적 파일은 바로 반환
    location ~ \.(js|css|png|jpg|gif|ico)$ {
        expires 7d;
    }
}

5. 테스트 파일 생성

www/index.php를 생성합니다.

<?php
phpinfo();

데이터베이스 연결을 테스트할 www/db_test.php를 생성합니다.

<?php
$host = 'mysql';  // 서비스 이름이며 localhost가 아님
$user = 'root';
$pass = 'root123456';
$db = 'test_db';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
    echo "데이터베이스 연결 성공!<br />";
    echo "MySQL 버전: " . $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
} catch(PDOException $e) {
    echo "연결 실패: " . $e->getMessage();
}

6. 서비스 시작

docker-compose up --build -d

--build 매개변수는 PHP 이미지를 다시 빌드한다는 뜻입니다. 처음 시작할 때는 기본 이미지를 다운로드하고 확장 기능을 설치하므로 시간이 더 걸릴 수 있습니다.

확인 및 테스트

1. PHP 테스트

http://localhost에 접속하면 phpinfo 페이지가 표시되어야 합니다. 다음 내용을 확인하세요.

  • PHP 버전이 8.1인지
  • mysqli, pdo_mysql, redis 확장 기능이 모두 enabled로 표시되는지

2. 데이터베이스 연결 테스트

http://localhost/db_test.php에 접속하면 “데이터베이스 연결 성공”과 MySQL 버전 번호가 표시되어야 합니다.

연결 실패 오류가 발생하면 다음을 확인하세요.

  • MySQL 컨테이너가 시작되었는지: docker-compose ps
  • host를 localhost로 작성했는지(올바른 값은 mysql)
  • 비밀번호가 정확한지

3. 컨테이너 상태 확인

docker-compose ps

모든 컨테이너의 State가 Up이어야 합니다.

4. 로그를 확인해 문제 해결

컨테이너가 시작되지 않으면 로그를 확인합니다.

docker-compose logs php  # PHP 컨테이너 로그 확인
docker-compose logs -f nginx  # Nginx 로그 실시간 확인(-f 매개변수)

로그에는 설정 파일 구문 오류나 사용 중인 포트처럼 정확한 원인이 표시됩니다.

팀 협업 모범 사례

버전 관리 전략

핵심 원칙: 설정은 업로드하고 민감한 정보는 업로드하지 않습니다

.gitignore 파일을 생성합니다.

# 민감한 정보는 업로드하지 않음
.env

# 데이터베이스 파일은 용량이 크므로 업로드하지 않음
mysql/data/

# 로그 파일은 업로드하지 않음
nginx/logs/*.log
php/logs/*.log

# 코드 디렉터리는 비즈니스 프로젝트에서 관리하며 DNMP에는 포함하지 않음
www/*
!www/.gitkeep  # 디렉터리 구조 유지

Git에 commit할 파일:

git add docker-compose.yml
git add .env.example  # 실제 비밀번호가 없는 예제 설정
git add nginx/conf.d/
git add php/Dockerfile
git add php/php.ini
git add mysql/my.cnf
git commit -m "feat: add DNMP environment config"
git push

신입 구성원 입사 시 과정(3분):

  1. 프로젝트 clone: git clone xxx
  2. 설정 복사: cp .env.example .env
  3. 비밀번호 변경: .env를 열어 MYSQL_ROOT_PASSWORD 수정
  4. 환경 시작: docker-compose up -d
  5. 데이터 가져오기: docker exec -i dnmp-mysql mysql -uroot -p < backup.sql

이것으로 끝입니다. 기존 구성원에게 “Nginx 설정은 어디에 있나요?”, “PHP에 어떤 확장 기능을 설치했나요?”라고 물을 필요가 없습니다. 모든 내용이 설정 파일에 있습니다.

여러 PHP 버전을 함께 사용하는 방법

상황: 기존 프로젝트는 PHP 7.4에서 실행되고 새 프로젝트는 PHP 8.1이 필요합니다. 어떻게 해야 할까요?

방법 1: docker-compose.yml에 여러 PHP 서비스 정의

services:
  php74:
    image: php:7.4-fpm
    container_name: dnmp-php74
    volumes:
      - ./www:/var/www/html
    networks:
      - dnmp-network

  php81:
    image: php:8.1-fpm
    container_name: dnmp-php81
    volumes:
      - ./www:/var/www/html
    networks:
      - dnmp-network

Nginx에서 프로젝트마다 서로 다른 PHP 버전을 가리키도록 설정:

nginx/conf.d/old-project.conf(기존 프로젝트):

server {
    listen 80;
    server_name old.local;
    root /var/www/html/old-project;

    location ~ \.php$ {
        fastcgi_pass php74:9000;  # PHP 7.4 지정
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

nginx/conf.d/new-project.conf(새 프로젝트):

server {
    listen 80;
    server_name new.local;
    root /var/www/html/new-project;

    location ~ \.php$ {
        fastcgi_pass php81:9000;  # PHP 8.1 지정
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

hosts 파일 설정:

127.0.0.1 old.local
127.0.0.1 new.local

http://old.local에 접속하면 PHP 7.4를 사용하고, http://new.local에 접속하면 PHP 8.1을 사용합니다. 서로 간섭하지 않습니다.

데이터 영속성과 백업

데이터 영속성은 볼륨 마운트로 이미 구현했습니다. ./mysql/data를 컨테이너의 /var/lib/mysql에 매핑했으므로 컨테이너를 삭제해도 데이터는 남습니다.

데이터베이스 백업 스크립트:

scripts/backup.sh를 생성합니다.

#!/bin/bash
BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d_%H%M%S)
MYSQL_CONTAINER="dnmp-mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="root123456"
DATABASE="test_db"

mkdir -p $BACKUP_DIR

echo "데이터베이스 $DATABASE 백업 시작..."
docker exec $MYSQL_CONTAINER mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD $DATABASE > $BACKUP_DIR/${DATABASE}_${DATE}.sql

echo "백업 완료: $BACKUP_DIR/${DATABASE}_${DATE}.sql"

정기 백업(cron 사용):

# crontab 편집
crontab -e

# 매일 새벽 2시에 백업
0 2 * * * /path/to/scripts/backup.sh

데이터베이스 복원:

docker exec -i dnmp-mysql mysql -uroot -proot123456 test_db < ./backups/test_db_20251218.sql

자주 발생하는 문제와 해결 가이드

문제 1: 포트가 사용 중이어서 컨테이너 시작 실패

증상:

Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use

확인 방법:

# Mac/Linux
lsof -i :80

# Windows
netstat -ano | findstr :80

해결 방법:

  • 방법 1: 80 포트를 사용 중인 프로그램(예: Apache, IIS)을 중지합니다.
  • 방법 2: 포트 매핑을 변경합니다. .env에서 NGINX_HTTP_PORT=8080으로 수정하고 localhost:8080에 접속합니다.

문제 2: 파일 권한 문제(Linux/Mac)

증상:

  • Nginx에 403 Forbidden 표시
  • PHP가 파일에 쓰지 못하고 Permission denied 오류 표시

원인:
컨테이너의 www-data 사용자(UID 33)와 호스트 사용자(UID 1000)가 일치하지 않아 마운트된 파일에 접근할 수 없습니다.

임시 방법(개발 환경):

chmod -R 777 ./www

올바른 방법(컨테이너 사용자 UID 수정):

컨테이너 사용자 UID가 호스트 사용자 UID와 일치하도록 php/Dockerfile을 수정합니다.

FROM php:8.1-fpm

# www-data 사용자의 UID를 호스트 사용자 UID(예: 1000)로 변경
RUN usermod -u 1000 www-data && groupmod -g 1000 www-data

# 기타 설정...

이미지를 다시 빌드합니다.

docker-compose build php
docker-compose up -d

문제 3: PHP 확장 기능 누락

증상:

Fatal error: Call to undefined function mysqli_connect()

확인 방법:

docker exec dnmp-php php -m  # 설치된 확장 기능 확인

mysqli가 표시되지 않으면 확장 기능이 설치되지 않은 것입니다.

해결 방법:

php/Dockerfile을 수정해 확장 기능을 추가합니다.

RUN docker-php-ext-install mysqli pdo_mysql

다시 빌드합니다.

docker-compose build php
docker-compose restart php

문제 4: 데이터베이스 연결 실패

증상:

SQLSTATE[HY000] [2002] Connection refused

자주 발생하는 실수:

  • 실수 1: host를 localhost 또는 127.0.0.1로 작성함
  • 실수 2: MySQL 컨테이너가 완전히 시작되기 전에 연결함
  • 실수 3: 비밀번호가 틀림

올바른 방법:

$host = 'mysql';  // localhost가 아니라 반드시 서비스 이름 사용
$user = 'root';
$pass = 'root123456';  // .env의 값과 같은지 확인

try {
    $pdo = new PDO("mysql:host=$host;dbname=test_db", $user, $pass);
    echo "연결 성공";
} catch(PDOException $e) {
    echo "실패: " . $e->getMessage();
}

그래도 연결되지 않으면 MySQL이 완전히 시작되었는지 확인합니다.

docker-compose logs mysql

mysqld: ready for connections가 표시되면 시작이 완료된 것입니다.

문제 5: 컨테이너가 시작된 직후 종료됨

증상:

docker-compose ps
# 특정 컨테이너의 Status에 Exit 1 또는 Exit 127 표시

확인 명령:

docker-compose logs 서비스이름

자주 발생하는 원인:

  1. 설정 파일 구문 오류:

    • Nginx 설정에서 세미콜론 ; 누락
    • docker-compose.yml 들여쓰기 오류(공백을 사용해야 하며 Tab은 사용할 수 없음)
  2. 환경 변수 누락:

    • MySQL에 MYSQL_ROOT_PASSWORD를 설정하지 않아 시작 실패
  3. 의존 서비스가 시작되지 않음:

    • Nginx가 PHP에 의존하지만 PHP 컨테이너가 시작되지 않음

해결 방법:
로그에 표시된 내용에 따라 설정 파일을 수정한 뒤 다시 시작합니다.

docker-compose down  # 모든 컨테이너 삭제
docker-compose up -d  # 다시 생성하고 시작

결론

여러 내용을 살펴봤지만, Docker Compose로 DNMP 환경을 배포할 때 얻는 핵심 가치는 다음 세 가지입니다.

  1. 시간 절약: 10분이면 끝나므로 절약한 시간에 커피를 두 잔 더 마실 수 있습니다.
  2. 문제 감소: 팀 환경이 통일되어 한밤중에 “내 컴퓨터에서는 되는데요”라는 bug를 긴급하게 조사할 일이 사라집니다.
  3. 유지보수 용이: 설정 파일 자체가 문서이므로 신입 구성원도 보면 바로 이해할 수 있고, 기존 구성원이 하나하나 알려줄 필요가 없습니다.

예전에는 LNMP를 수동으로 설치하다가 새벽 두 시가 되어도 실행하지 못하는 일이 자주 있었습니다. Docker Compose를 사용한 뒤부터는 말 그대로 “up하고 커피 한 잔 마시면 환경이 준비되는” 경험을 하고 있습니다.

망설이지 말고 퇴근 전에 10분만 투자해 보세요. 먼저 오픈 소스 프로젝트 imeepo/dnmp를 clone해 실행하면서 한 번에 시작하는 편리함을 느껴 보세요. 익숙해진 뒤에는 설정 파일을 직접 작성하며 더 깊이 학습할 수 있습니다.

문제가 생겨도 당황하지 마세요.

  1. 먼저 docker-compose logs로 로그를 확인합니다.
  2. GitHub Issues를 검색합니다. 대부분 누군가 같은 문제를 겪었습니다.
  3. 그래도 해결되지 않으면 프로젝트 저장소에 Issue를 등록하세요. 커뮤니티가 활발합니다.

마지막으로 Docker Compose는 PHP 환경만 배포할 수 있는 도구가 아닙니다. Node.js, Python, Go에도 사용할 수 있습니다. 이 방식을 익혀 두면 나중에 기술 스택이 바뀌어도 환경 설정을 걱정할 필요가 없습니다.

다음 단계:

  • 바로 실행: git clone https://github.com/imeepo/dnmp.git 명령으로 첫 DNMP 환경을 실행합니다.
  • 심화 학습: Docker Compose 공식 문서에서 docker-compose.override.yml 같은 고급 사용법을 익힙니다.
  • 공유: 이 글이 도움이 되었다면 아직 환경을 수동으로 설치하는 동료에게 공유해 같은 불편에서 벗어나도록 도와주세요.

Docker Compose로 PHP 환경을 한 번에 배포하는 전체 과정

DNMP 완전 가이드(Nginx+MySQL+PHP)를 따라 10분 만에 팀별 환경 차이 문제를 완전히 해결합니다.

⏱️ Estimated time: 10 min

  1. 1

    Step 1: 문제 배경과 해결책 이해하기

    문제 배경:
    • 팀원마다 개발 환경이 서로 다릅니다.
    • 신입 구성원은 환경 설정에만 반나절이 걸리고, 기존 구성원이 일대일로 안내해야 합니다.
    • A 컴퓨터에서 잘 실행되던 코드가 B 컴퓨터에서는 오류를 냅니다.
    • 서버 배포도 운에 맡기는 상황입니다.
    • PHP 버전이 일치하지 않습니다(7.4와 8.1).
    • MySQL 버전이 일치하지 않습니다(5.7과 8.0).

    해결책:
    • Docker Compose로 DNMP(Docker+Nginx+MySQL+PHP) 개발 환경을 한 번에 배포합니다.
    • 10분이면 끝나며 팀별 환경 차이 문제를 완전히 해결합니다.
    • 모든 팀원이 같은 환경 설정을 사용합니다.
  2. 2

    Step 2: docker-compose.yml 설정 및 서비스 시작하기

    전체 설정:
    • docker-compose.yml에 Nginx, MySQL, PHP 세 서비스를 설정합니다.
    • 네트워크 연결을 설정합니다.
    • 데이터 영속성을 설정합니다.
    • 환경 변수를 설정합니다.
    • 서비스가 정상적으로 시작되도록 헬스 체크를 설정합니다.

    10분 배포 과정:
    1) docker-compose.yml 파일을 생성합니다.
    2) Nginx, MySQL, PHP 서비스를 설정합니다.
    3) 서비스를 시작합니다: docker-compose up -d
    4) 서비스 실행 상태를 확인합니다: docker-compose ps
    5) PHP 애플리케이션을 테스트합니다: http://localhost 접속
  3. 3

    Step 3: 자주 발생하는 문제와 모범 사례

    자주 발생하는 문제:
    • Nginx가 PHP-FPM의 socket 파일을 찾지 못합니다.
    • MySQL 연결에 실패합니다.
    • PHP 확장 기능이 누락되었습니다.
    • 설정 파일이 적용되지 않습니다.

    해결 방법:
    • 로그에 표시된 내용에 따라 설정 파일을 수정합니다.
    • 그런 다음 다시 시작합니다: docker-compose down, docker-compose up -d

    모범 사례:
    • 버전 태그로 이미지 버전을 고정합니다.
    • 데이터 영속성을 설정합니다.
    • 헬스 체크를 설정합니다.
    • 환경 변수로 설정을 관리합니다.
    • 이미지 버전을 정기적으로 업데이트합니다.
    • 설정 설명을 문서화합니다.

    다음 단계:
    • 바로 실행: git clone https://github.com/imeepo/dnmp.git 명령으로 첫 DNMP 환경을 실행합니다.
    • 심화 학습: Docker Compose 공식 문서에서 고급 사용법을 익힙니다.
    • 공유: 아직 환경을 수동으로 설치하는 동료에게 공유합니다.

FAQ

Docker Compose로 PHP 환경을 배포해야 하는 이유는 무엇인가요?
문제 배경:
• 팀원마다 개발 환경이 서로 다릅니다.
• 신입 구성원은 환경 설정에만 반나절이 걸리고, 기존 구성원이 일대일로 안내해야 합니다.
• A 컴퓨터에서 잘 실행되던 코드가 B 컴퓨터에서는 오류를 냅니다.
• 서버 배포도 운에 맡기는 상황입니다.
• PHP 버전이 일치하지 않습니다(7.4와 8.1).
• MySQL 버전이 일치하지 않습니다(5.7과 8.0).

해결책: Docker Compose로 DNMP(Docker+Nginx+MySQL+PHP) 개발 환경을 한 번에 배포하면 10분 만에 팀별 환경 차이 문제를 완전히 해결하고 모든 팀원이 같은 환경 설정을 사용할 수 있습니다.
Docker Compose로 DNMP 환경을 배포하는 방법은 무엇인가요?
전체 설정:
• docker-compose.yml에 Nginx, MySQL, PHP 세 서비스를 설정합니다.
• 네트워크 연결을 설정합니다.
• 데이터 영속성을 설정합니다.
• 환경 변수를 설정합니다.
• 서비스가 정상적으로 시작되도록 헬스 체크를 설정합니다.

10분 배포 과정:
1) docker-compose.yml 파일을 생성합니다.
2) Nginx, MySQL, PHP 서비스를 설정합니다.
3) 서비스를 시작합니다: docker-compose up -d
4) 서비스 실행 상태를 확인합니다: docker-compose ps
5) PHP 애플리케이션을 테스트합니다: http://localhost 접속
DNMP 환경 배포 시 자주 발생하는 문제는 무엇인가요?
자주 발생하는 문제:
• Nginx가 PHP-FPM의 socket 파일을 찾지 못합니다.
• MySQL 연결에 실패합니다.
• PHP 확장 기능이 누락되었습니다.
• 설정 파일이 적용되지 않습니다.

해결 방법:
• 로그에 표시된 내용에 따라 설정 파일을 수정합니다.
• 그런 다음 다시 시작합니다: docker-compose down, docker-compose up -d

점검 순서:
• 컨테이너 로그 확인: docker-compose logs
• 서비스 상태 확인: docker-compose ps
• 네트워크 연결 확인: docker network inspect
• 설정 파일의 정확성 확인
Docker Compose PHP 환경의 모범 사례는 무엇인가요?
모범 사례:
• 버전 태그로 이미지 버전을 고정합니다.
• 데이터 영속성을 설정합니다.
• 헬스 체크를 설정합니다.
• 환경 변수로 설정을 관리합니다.
• 이미지 버전을 정기적으로 업데이트합니다.
• 설정 설명을 문서화합니다.

다음 단계:
• 바로 실행: git clone https://github.com/imeepo/dnmp.git 명령으로 첫 DNMP 환경을 실행합니다.
• 심화 학습: Docker Compose 공식 문서에서 고급 사용법을 익힙니다.
• 공유: 아직 환경을 수동으로 설치하는 동료에게 공유해 같은 불편에서 벗어나도록 도와줍니다.

5분 읽기 · 게시일: 2025년 12월 18일 · 수정일: 2026년 9월 4일

댓글

GitHub로 로그인하여 댓글을 남기세요

Easton BlogEaston Blog