📚 /42seoul 시리즈

1. 소개


One container is not enough, WE NEED TO GO DEEPER

본과정에서 열다섯번째로 진행한 과제로, 가상 머신에서 Docker Compose를 사용해 여러 서비스로 구성된 소규모 인프라를 구축하는 과제이다. 실습도 실습이지만, 평가자에게 설명하기 위해서라도 많은 내용을 공부해야 했다.



2. inception 명세서




3. 개념 정리


  1. vm,docker 부분은 이전 과제인 born2beroot와 유사하므로 넘어가자
  2. 컨테이너의 연결 방식은 마지막 과제인 ft_transcendence에서도 쓰일 수 있으니 공부하자
  3. 전체적으로 과제를 진행하며 궁금했던 난잡한 구조의 질문들을 정리해보았다.
  4. 각 개념만으로도 전공서적 수십 권 분량의 방대한 내용이 존재하므로, 이번 과제의 이해에 필요한 핵심만 간략히 서술하였다.

3-1. Docker compose

Docker Compose는 여러 개의 컨테이너 서비스를 하나의 YAML 파일(보통 docker‑compose.yml)로 정의하고, 한 번의 명령으로 일괄 빌드·실행·종료·삭제까지 제어할 수 있도록 돕는 도구이다.

COMPOSE=docker compose

.PHONY: up down logs restart

up:
	$(COMPOSE) up -d --build

down:
	$(COMPOSE) down -v

logs:
	$(COMPOSE) logs -f --tail=100

restart: down up

3-2. Nginx

Nginx는 이벤트 기반 아키텍처를 사용해 정적 파일‑서빙, 리버스 프록시, 로드 밸런싱을 처리하는 오픈소스 웹 서버이다.


3-2. 무한 루프 금지

무한 루프 금지란, Docker 컨테이너 내부에서 tail -f, bash, sleep infinity, while true와 같은 무한 루프를 사용하지 말라는 의미이다. 이는 Docker가 VM과 다르게 자원의 효율적인 사용을 요구하기 때문이다. 무한 루프가 포함된 프로세스는 자원을 낭비하며, 이는 컨테이너가 설계된 목적에 부합하지 않는다. 따라서, 이러한 루프를 피하고 다른 방식으로 작업을 처리하는 것이 좋다.


3-3. PID 1

컨테이너가 시작될 때 가장 먼저 실행되는 프로세스가 PID 1을 차지한다. 리눅스에서 PID 1은 일반 프로세스와 달리 특별한 임무를 가진다.


3-4. .env

.env 파일은 프로젝트 루트에 두는 평문 설정 파일이며, KEY=VALUE 형식으로 환경 변수를 선언해 둔 목록이다. 즉, “코드는 동일, 설정은 외부화” 원칙을 실천해, 컨테이너 기반 서비스의 배포·보안·유지보수를 간결하게 만드는 핵심 도구이다.


3‑5. TLS 1.2 / 1.3

TLS(Transport Layer Security)는 애플리케이션 계층과 전송 계층 사이에서 암호화·무결성·인증을 제공해, HTTP·SMTP·FTP 등 모든 TCP 프로토콜을 “https” 형태의 보안 터널로 감싸는 표준이다.



4. Mandatory


우리는 총 3개의 컨테이너를 구성해야 한다. (nginx, wordpress,mariadb)

서버스에 접근하는 모든 요청은 먼저 nginx로 전달되며, nginx는 정적 파일을 직접 제공하고, 동적 요소는 wordpress(PHP) 컨테이너로 프록시하여 처리한다. 그리고 워드프레스에서 사용하는 게시글, 사용자 정보 등 서비스의 모든 데이터는 mariadb 컨테이너에서 관리하게 된다.

4-1. Makefile

all: build up

build:
	$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) build --no-cache

up:
	$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) up -d

stop:
	$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) stop

down:
	$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) down

clean:
	$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) down --volumes --rmi all

# 정리: 사용하지 않는 이미지, 캐시 및 네트워크 제거
fclean:
	$(DOCKER_COMPOSE) -f $(COMPOSE_FILE) down --volumes --rmi all
	docker system prune -a --volumes -f
	rm -rf $(MARIADB_LOCAL_VOLUME) $(WORDPRESS_LOCAL_VOLUME)
	sleep 5
	mkdir -p $(MARIADB_LOCAL_VOLUME) $(WORDPRESS_LOCAL_VOLUME)
	chmod 777 * $(MARIADB_LOCAL_VOLUME) $(WORDPRESS_LOCAL_VOLUME)

# 볼륨 디렉토리 초기화
reset-volumes:
	rm -rf $(MARIADB_LOCAL_VOLUME) $(WORDPRESS_LOCAL_VOLUME)
	mkdir -p $(MARIADB_LOCAL_VOLUME) $(WORDPRESS_LOCAL_VOLUME)
	chmod 777 * $(MARIADB_LOCAL_VOLUME) $(WORDPRESS_LOCAL_VOLUME)

4-2. Docker-compose.yml

# version: '3.7'

services:
  mariadb:
    build:
      context: ./requirements/mariadb
      dockerfile: Dockerfile
    image: mariadb
    expose:
      - "3306"
    env_file:
      - .env
    volumes:
      - mariadb_data:/var/lib/mysql
    container_name: mariadb
    restart: always
    networks:
      - 42_network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  wordpress:
    build:
      context: ./requirements/wordpress
      dockerfile: Dockerfile
    image: wordpress
    depends_on:
      mariadb:
        condition: service_healthy
    expose:
      - "9000"
    env_file:
      - .env
    volumes:
      - wordpress_data:/var/www/html/wordpress
    container_name: wordpress
    restart: always
    networks:
      - 42_network
    healthcheck:
      test: ["CMD-SHELL", "pgrep php-fpm7.4 || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 10s

  nginx:
    build:
      context: ./requirements/nginx
      dockerfile: Dockerfile
      args:
        MY_SSL_PATH: ${MY_SSL_PATH}
        WORDPRESS_URL: ${WORDPRESS_URL}
    image: nginx
    ports:
      - "443:443"
    container_name: nginx
    depends_on:
      wordpress:
        condition: service_healthy
    env_file:
      - .env
    volumes:
      - wordpress_data:/var/www/html/wordpress
    restart: always
    networks:
      - 42_network

volumes:
  mariadb_data:
    driver: local
    driver_opts:
      type: 'none'
      device: ${MARIADB_LOCAL_VOLUME}
      o: 'bind'

  wordpress_data:
    driver: local
    driver_opts:
      type: 'none'
      device: ${WORDPRESS_LOCAL_VOLUME}
      o: 'bind'

networks:
  42_network:
    name: 42_network
    driver: bridge

4-3. Dockerfile


4-3-1. nginx

# Debian Linux
FROM debian:11

ARG MY_SSL_PATH
ARG WORDPRESS_URL

# 필수 패키지 설치
RUN apt-get update && apt-get install -y --no-install-recommends \
    nginx \
    openssl \
    curl \
    dumb-init && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# 인증서와 키 파일을 포함시킬 디렉토리 생성
RUN mkdir -p ${MY_SSL_PATH} && chmod -R 755 ${MY_SSL_PATH}

# SSL 인증서와 키 생성
RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout ${MY_SSL_PATH}/server.key \
    -out ${MY_SSL_PATH}/server.crt \
    -subj "/C=/ST=/L=/O=/OU=/CN="

# 컨테이너 내부에서 필요한 파일 복사
COPY conf/nginx.conf /etc/nginx/nginx.conf

# 설정 파일 내의 변수 설정
RUN sed -i "s|__WORDPRESS_URL__|${WORDPRESS_URL}|g" /etc/nginx/nginx.conf && \
    sed -i "s|__MY_SSL_PATH__|${MY_SSL_PATH}|g" /etc/nginx/nginx.conf

# 443 포트 개방
EXPOSE 443

# dumb-init을 엔트리포인트로 설정하고 Nginx를 실행
ENTRYPOINT ["/usr/bin/dumb-init", "--"]

# Nginx를 포그라운드 모드로 실행
CMD ["nginx", "-g", "daemon off;"]

4-3-2. wordpress

# Dockerfile (wordpress)
FROM debian:11

RUN apt-get update && apt-get install -y --no-install-recommends \
    wget \
    unzip \
    curl \
    php \
    php7.4-fpm \
    php7.4-json \
    php7.4-mbstring \
    php7.4-mysql \
    php7.4-phar \
    less \
    bash \
    netcat-openbsd \
    dumb-init \
    ca-certificates && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

RUN id -u www-data || useradd -r -s /usr/sbin/nologin -d /var/www/html -U www-data

COPY conf/php-fpm.conf /etc/php/7.4/fpm/php-fpm.conf
COPY conf/www.conf /etc/php/7.4/fpm/pool.d/www.conf

RUN mkdir -p /var/log/php7.4-fpm && \
    chown -R www-data:www-data /var/log/php7.4-fpm && \
    chmod -R 755 /var/log/php7.4-fpm

# WordPress 파일 다운로드 (임시 경로)
RUN mkdir -p /usr/src/wordpress && \
    wget https://wordpress.org/latest.zip -O /tmp/wordpress.zip && \
    unzip /tmp/wordpress.zip -d /usr/src/ && \
    rm /tmp/wordpress.zip

COPY tools/wp_setup.sh /usr/local/bin/wp_setup.sh
RUN chmod +x /usr/local/bin/wp_setup.sh

EXPOSE 9000

ENTRYPOINT ["/usr/bin/dumb-init", "--"]

CMD ["/bin/bash", "-c", "/usr/local/bin/wp_setup.sh && exec php-fpm7.4 -F"]

4-3-3. mariadb

FROM debian:11

# 필수 패키지 설치
RUN apt-get update && apt-get install -y --no-install-recommends \
    mariadb-server \
    mariadb-client \
    dumb-init \
    bash \
    curl && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# bind-address 설정: 외부 접속 가능하도록
RUN sed -i 's/^bind-address\s*=.*/bind-address = 0.0.0.0/' /etc/mysql/mariadb.conf.d/50-server.cnf

# 데이터 디렉토리 및 mysqld 소켓 디렉토리 권한 설정
# VOLUME ["/var/lib/mysql"]
RUN mkdir -p /run/mysqld && chown -R mysql:mysql /run/mysqld /var/lib/mysql

# 초기화 스크립트 복사
COPY tools/db_setup.sh /docker-entrypoint-initdb.d/
RUN chmod +x /docker-entrypoint-initdb.d/db_setup.sh

# 엔트리포인트 스크립트 복사
COPY tools/entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh

# 환경 변수 설정 (필요에 따라 빌드 시나 docker run/docker compose 시 오버라이드 가능)
ENV MYSQL_ROOT_PASSWORD=secret
ENV MYSQL_DATABASE=mydb
ENV MYSQL_USER=myuser
ENV MYSQL_PASSWORD=mypassword

# MariaDB 포트 개방
EXPOSE 3306

# dumb-init을 EntryPoint로 설정 + 커스텀 엔트리포인트 스크립트 실행
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/entrypoint.sh"]


5. Evaluation


2025.05 코드 리뷰 추가 삽입

try1 - review 1

try1 - reivew 2

try1 - review 3

가장 아쉬웠던 과제로 Dockerfile 관리이 특히 아쉬웠다. 다시 열어보니 구성이 지나치게 복잡하고 난잡했다. 평가에서도 “정말 필요한 기능인가?”, “굳이 이렇게까지 해야 하나?”라는 지적을 받았지만, 명확한 답을 내놓지 못했다. 예를 들어, 컨테이너를 restart: always로 돌리면서도 별도로 HEALTHCHECK를 넣은 부분은 “어차피 재시작할 텐데 필요할까?”라는 의문을 남겼다.

이 경험을 바탕으로 다음 프로젝트에서는 Dockerfile을 최대한 간결하게 유지하려 한다. 불필요한 기능은 과감히 제거하고, 베이스 이미지는 가벼운 Alpine으로 통일해 깔끔한 구성을 목표로 하고 있다.



6. Reference