Docker Compose를 이용하여 다음 기준에 맞게 MySQL을 설치해 보겠습니다.
설치 기준
- MySQL 이미지
- 컨테이너가 호스트의 UID/GID로 실행
- 데이터, 설정, 로그를 볼륨으로 분리 관리
- 전용 네트워크 생성
- 로그 관리 (stdout + 파일 로그)
⚙️ 디렉토리 구조 예시
mysql/
├─ compose.yaml
├─ data/ # MySQL 실제 데이터 (호스트에 저장)
├─ logs/ # 로그 파일 저장
└─ conf.d/ # 추가 설정 (예: my.cnf)
⚙️ compose.yaml
version: "3.9"
services:
mysql:
image: mysql:8.4
container_name: mysql84
user: "${UID}:${GID}" # 현재 사용자 UID/GID 사용
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpass}
MYSQL_DATABASE: ${MYSQL_DATABASE:-appdb}
MYSQL_USER: ${MYSQL_USER:-appuser}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-apppass}
TZ: Asia/Seoul
ports:
- "3306:3306"
volumes:
- ./data:/var/lib/mysql
- ./conf.d:/etc/mysql/conf.d
- ./logs:/var/log/mysql
networks:
- mysql_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
mysql_net:
name: mysql_network
driver: bridge
⚙️ 환경 변수 파일: .env (같은 디렉토리에)
UID=$(id -u)
GID=$(id -g)
MYSQL_ROOT_PASSWORD=rootpass
MYSQL_DATABASE=appdb
MYSQL_USER=appuser
MYSQL_PASSWORD=apppass
⚙️ Disk Permission
1) securityContext 확인
Dockerfile이나 compose.yaml에서 정의할 UID, GID를 확인합니다.
ex) mysql 1001
2) Create User
먼저 해야 할 일은 mysql을 설치할 계정을 생성합니다.
● Create mysql User
groupadd -g 1001 -r mysql
useradd -c "mysql" -u 1001 -g mysql -s /bin/bash -r -p passwd mysql
usermod -aG docker mysql
● Create Docker User
groupadd -g 999 -r docker
useradd -c "docker" -u 999 -g docker -s /bin/bash -r -p passwd docker
usermod -aG docker docker
3) Directory Permission
chown -R mysql:mysql ./data
chown -R mysql:mysql ./conf.d
chown -R mysql:mysql ./logs
⚙️ mysql 설치
mysql 계정으로 login
⚠️ docker compose는 .env 파일의 변수 확장을 지원하지만, $(id -u) 같은 명령어 치환은 바로 안 됩니다.
따라서 아래처럼 쉘에서 직접 설정 후 실행하는 것이 좋습니다.
export UID=$(id -u)
export GID=$(id -g)
docker compose -f compose.yaml up -d
⚙️my.cnf 설정
conf.d/my.cnf 파일을 만들어서 로그 파일 위치나 설정을 조정할 수 있습니다.
예시 (conf.d/my.cnf):
[mysqld]
# 로그 설정
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
# 대소문자 구분 안 함 (테이블 이름)
lower_case_table_names = 1
# 기타 권장 설정
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
⚙️ Docker compose 실행
# UID/GID 환경변수 설정 후 실행
export UID=$(id -u)
export GID=$(id -g)
docker compose -f compose.yaml up -d
이 구성으로 다음이 가능합니다.
- 컨테이너가 호스트 사용자 권한(UID/GID) 으로 실행되어 퍼미션 문제 최소화
- 데이터/로그/설정을 개별 볼륨으로 관리
- 전용 네트워크로 다른 서비스(MySQL 클라이언트 등)와 연동 가능
- 로그 파일 + Docker 로그 드라이버 동시 관리
컨테이너 내부 또는 외부에서 다음처럼 접속할 수 있습니다.
⚙️ 컨테이너 내부 접속
docker exec -it mysql8 mysql -uappuser -papppass demodb
또는 MySQL 클라이언트로 접속
sudo apt install mysql-client-core-8.0
mysql -h 127.0.0.1 -P 3306 -uappuser -papppass demodb
item 테이블 생성 쿼리
CREATE TABLE IF NOT EXISTS item (
item_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '상품 ID',
item_name VARCHAR(100) NOT NULL COMMENT '상품명',
category VARCHAR(50) DEFAULT NULL COMMENT '카테고리',
price DECIMAL(10,2) NOT NULL COMMENT '가격',
stock INT DEFAULT 0 COMMENT '재고 수량',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '등록일시',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일시'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
샘플 데이터 삽입 쿼리
INSERT INTO item (item_name, category, price, stock)
VALUES
('USB Type-C Cable', 'Electronics', 9.99, 120),
('Wireless Mouse', 'Electronics', 24.50, 80),
('Mechanical Keyboard', 'Electronics', 79.00, 40),
('Notebook A5', 'Stationery', 3.50, 200),
('Ballpoint Pen', 'Stationery', 1.20, 500),
('Coffee Mug', 'Kitchen', 7.80, 150);
확인 쿼리
SELECT * FROM item;
init.sql 파일로 만들어서 Docker Compose에서 자동 초기화 설정
MySQL의 Docker 공식 이미지는 /docker-entrypoint-initdb.d/ 경로에 .sql 파일을 넣으면,
컨테이너 최초 실행 시 자동으로 실행됩니다.
이걸 이용해서 init.sql로 테이블과 샘플 데이터를 자동으로 세팅할 수 있습니다.
1️⃣ 디렉터리 구조 예시
mysql/
├─ compose.yaml
├─ data/
├─ logs/
├─ conf.d/
│ └─ my.cnf
└─ init/
└─ init.sql
2️⃣ compose.yaml 수정
init.sql을 마운트하도록 volumes 항목을 수정합니다.
version: "3.9"
services:
mysql:
image: mysql:8.4
container_name: mysql8
user: "${UID}:${GID}"
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpass}
MYSQL_DATABASE: ${MYSQL_DATABASE:-demodb}
MYSQL_USER: ${MYSQL_USER:-appuser}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-apppass}
TZ: Asia/Seoul
ports:
- "3306:3306"
volumes:
- ./data:/var/lib/mysql
- ./conf.d:/etc/mysql/conf.d
- ./logs:/var/log/mysql
- ./init:/docker-entrypoint-initdb.d # 초기화 SQL 파일
networks:
- mysql_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
mysql_net:
name: mysql_network
driver: bridge
3️⃣ init/init.sql 파일 내용
-- 데이터베이스 선택
USE demodb;
-- item 테이블 생성
CREATE TABLE IF NOT EXISTS item (
item_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '상품 ID',
item_name VARCHAR(100) NOT NULL COMMENT '상품명',
category VARCHAR(50) DEFAULT NULL COMMENT '카테고리',
price DECIMAL(10,2) NOT NULL COMMENT '가격',
stock INT DEFAULT 0 COMMENT '재고 수량',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '등록일시',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일시'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 샘플 데이터 삽입
INSERT INTO item (item_name, category, price, stock) VALUES
('USB Type-C Cable', 'Electronics', 9.99, 120),
('Wireless Mouse', 'Electronics', 24.50, 80),
('Mechanical Keyboard', 'Electronics', 79.00, 40),
('Notebook A5', 'Stationery', 3.50, 200),
('Ballpoint Pen', 'Stationery', 1.20, 500),
('Coffee Mug', 'Kitchen', 7.80, 150);
✅ 이 설정으로 컨테이너가 처음 시작될 때 자동으로 item 테이블 생성 + 데이터 삽입까지 완료됩니다.
다음에 docker compose down / up을 반복해도, 이미 생성된 데이터(./data)는 유지됩니다.
운영용 초기 스크립트 세트 구조
MySQL Docker 공식 이미지는 /docker-entrypoint-initdb.d/ 안에 있는 모든 .sql, .sql.gz, .sh 파일을 이름 순서대로 자동 실행합니다.
따라서 파일명을 숫자 접두사로 지정하면 실행 순서를 제어할 수 있습니다.
이 방식은 실제 서비스 환경에서도 데이터베이스 초기화/이관 자동화에 자주 쓰입니다.
1️⃣ 디렉터리 구조
mysql/
├─ compose.yaml
├─ data/
├─ logs/
├─ conf.d/
│ └─ my.cnf
└─ init/
├─ 01-schema.sql # 스키마 정의
├─ 02-data.sql # 초기 데이터
├─ 03-indexes.sql # 인덱스 및 제약조건
├─ 04-views.sql # 뷰 정의
└─ 05-users.sql # 추가 사용자/권한 설정
2️⃣ compose.yaml
이전과 동일하게 init 디렉터리 전체를 /docker-entrypoint-initdb.d/로 마운트합니다.
version: "3.9"
services:
mysql:
image: mysql:8.4
container_name: mysql8
user: "${UID}:${GID}"
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpass}
MYSQL_DATABASE: ${MYSQL_DATABASE:-demodb}
MYSQL_USER: ${MYSQL_USER:-appuser}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-apppass}
TZ: Asia/Seoul
ports:
- "3306:3306"
volumes:
- ./data:/var/lib/mysql
- ./conf.d:/etc/mysql/conf.d
- ./logs:/var/log/mysql
- ./init:/docker-entrypoint-initdb.d # 모든 초기화 SQL 자동 실행
networks:
- mysql_net
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
mysql_net:
name: mysql_network
driver: bridge
3️⃣ SQL 파일 예시
01-schema.sql
USE demodb;
CREATE TABLE IF NOT EXISTS item (
item_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '상품 ID',
item_name VARCHAR(100) NOT NULL COMMENT '상품명',
category VARCHAR(50) DEFAULT NULL COMMENT '카테고리',
price DECIMAL(10,2) NOT NULL COMMENT '가격',
stock INT DEFAULT 0 COMMENT '재고 수량',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '등록일시',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일시'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
02-data.sql
USE demodb;
INSERT INTO item (item_name, category, price, stock) VALUES
('USB Type-C Cable', 'Electronics', 9.99, 120),
('Wireless Mouse', 'Electronics', 24.50, 80),
('Mechanical Keyboard', 'Electronics', 79.00, 40),
('Notebook A5', 'Stationery', 3.50, 200),
('Ballpoint Pen', 'Stationery', 1.20, 500),
('Coffee Mug', 'Kitchen', 7.80, 150);
03-indexes.sql
03-indexes.sql
USE demodb;
-- 인덱스 생성
CREATE INDEX idx_item_name ON item (item_name);
CREATE INDEX idx_category ON item (category);
04-views.sql
04-views.sql
USE demodb;
-- 상품 재고 요약 뷰
CREATE OR REPLACE VIEW view_item_summary AS
SELECT
category,
COUNT(*) AS total_items,
SUM(stock) AS total_stock,
ROUND(AVG(price), 2) AS avg_price
FROM item
GROUP BY category;
05-users.sql
05-users.sql
USE demodb;
-- 읽기 전용 사용자 생성 (운영용 예시)
CREATE USER IF NOT EXISTS 'readonly'@'%' IDENTIFIED BY 'readonlypass';
GRANT SELECT ON demodb.* TO 'readonly'@'%';
-- 권한 적용
FLUSH PRIVILEGES;
4️⃣ 실행 절차
- 초기화 필요 시 데이터 폴더 정리
- docker compose down
- UID/GID 설정 후 mysql 설치
- 데이터, 인덱스, 뷰, 유저 모두 자동 적용 확인
docker exec -it mysql8 mysql -uappuser -papppass demodb -e "SHOW TABLES;"
| Tables_in_demodb |
|---|
| item |
| view_item_summary |
docker exec -it mysql8 mysql -uappuser -papppass demodb -e "SELECT * FROM view_item_summary;"
| category | total_items | total_stock | avg_price |
|---|---|---|---|
| Electronics | 3 | 240 | 37.83 |
| Kitchen | 1 | 150 | 7.80 |
| Stationery | 2 | 700 | 2.35 |
| 파일명 | 역할 |
|---|---|
01-schema.sql |
테이블 및 스키마 정의 |
02-data.sql |
기본 데이터 삽입 |
03-indexes.sql |
인덱스 및 제약조건 추가 |
04-views.sql |
뷰 정의 |
05-users.sql |
운영용 계정 및 권한 설정 |
이 구조는 실제 운영에서도 DB 초기 설정 및 재현 테스트에 유용하며, 파일별로 변경 추적(Git 관리)도 쉬워집니다.
SQL 구조를 CI/CD 파이프라인 (예: GitHub Actions, Jenkins) 에서 자동으로 테스트 및 배포되게 구성하는 YAML 워크플로 구성
지금까지 만든 MySQL 초기화 SQL 세트를 CI/CD 파이프라인에서 자동 테스트 및 배포하도록 아래 두 가지 방법으로 CI/CD Pipeline을 작성해 보겠습니다.
✅ GitHub Actions
✅ Jenkins Pipeline
1️⃣ GitHub Actions 워크플로 예시
파일 경로: .github/workflows/mysql-ci.yml
이 워크플로는 다음을 수행합니다:
- MySQL 8.4 컨테이너를 띄움
init/*.sql파일을 순서대로 실행- DB 스키마/데이터 테스트 (테이블, 뷰, 유저 확인)
- 성공 시 “배포 단계” (예: Docker Hub 푸시 등) 로 확장 가능
name: MySQL CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
jobs:
mysql-ci:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.4
env:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: demodb
MYSQL_USER: appuser
MYSQL_PASSWORD: apppass
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost -prootpass"
--health-interval=5s
--health-timeout=2s
--health-retries=10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Wait for MySQL to be ready
run: |
echo "Waiting for MySQL..."
for i in {1..20}; do
if mysql -h 127.0.0.1 -P 3306 -uroot -prootpass -e "SELECT 1;" > /dev/null 2>&1; then
echo "MySQL is up!"
break
fi
sleep 3
done
- name: Run init SQL scripts
run: |
for f in $(ls -1 ./mysql/init/*.sql | sort); do
echo "Running $f"
mysql -h 127.0.0.1 -P 3306 -uroot -prootpass demodb < "$f"
done
- name: Validate schema and data
run: |
echo "Checking tables..."
mysql -h 127.0.0.1 -P 3306 -uroot -prootpass demodb -e "SHOW TABLES;"
echo "Checking view..."
mysql -h 127.0.0.1 -P 3306 -uroot -prootpass demodb -e "SELECT * FROM view_item_summary;"
echo "Checking user..."
mysql -h 127.0.0.1 -P 3306 -uroot -prootpass -e "SELECT user, host FROM mysql.user WHERE user='readonly';"
- name: CI Completed
run: echo "MySQL schema/data validated successfully!"
확장 (CD 단계)
- Docker Image Build + Push
- name: Build and Push Docker Image run: | docker build -t yourrepo/mysql-demo:latest . echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin docker push yourrepo/mysql-demo:latest- 배포 서버로 전송 (SSH or Kubernetes)
GitHub Secrets를 사용해서 배포 서버에 자동 반영도 가능합니다.
2️⃣ Jenkins Pipeline 예시 (Declarative)
파일: Jenkinsfile
이 파이프라인은 Jenkins에서 다음을 수행합니다:
- Git 리포지토리에서 소스 체크아웃
- Docker Compose로 MySQL 서비스 실행
- SQL 스크립트 적용 및 테스트
- 성공 시 자동 배포 또는 알림
pipeline {
agent any
environment {
MYSQL_ROOT_PASSWORD = 'rootpass'
MYSQL_DATABASE = 'demodb'
MYSQL_USER = 'appuser'
MYSQL_PASSWORD = 'apppass'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build & Init MySQL') {
steps {
sh '''
docker compose -f mysql/docker-config.yaml down
rm -rf mysql/data/*
export UID=$(id -u)
export GID=$(id -g)
docker compose -f mysql/docker-config.yaml up -d
'''
}
}
stage('Wait for MySQL') {
steps {
sh '''
echo "Waiting for MySQL to start..."
for i in {1..20}; do
if docker exec mysql8 mysqladmin ping -prootpass --silent; then
echo "MySQL is ready!"
break
fi
sleep 3
done
'''
}
}
stage('Run SQL Tests') {
steps {
sh '''
docker exec mysql8 mysql -uappuser -papppass demodb -e "SHOW TABLES;"
docker exec mysql8 mysql -uappuser -papppass demodb -e "SELECT * FROM view_item_summary;"
docker exec mysql8 mysql -uroot -prootpass -e "SELECT user, host FROM mysql.user WHERE user='readonly';"
'''
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
echo 'Deploying to production...'
// 예시: SSH, Helm, Ansible 등과 연계 가능
}
}
}
post {
always {
sh 'docker compose -f mysql/docker-config.yaml down'
}
success {
echo 'MySQL CI/CD pipeline completed successfully!'
}
failure {
echo 'Build failed!'
}
}
}
이상으로 GitHub Actions과 Jenkins을 이용하여 CI/CD Pipeline 를 작성해 보았습니다.