리눅스
라라벨 개발 환경을 도커 컨테이너로 구성하는 방법
변군이글루
2025. 11. 20. 14:36
반응형
라라벨 개발 환경을 도커 컨테이너로 구성하는 방법
테스트 환경
nginx 버전 정보
docker compose exec nginx nginx -v
php 버전 정보
docker compose exec php-fpm php --version
composer 버전 정보
docker compose exec php-fpm composer --version
라라벨 artisan 버전 정보
docker compose exec php-fpm php artisan --version
작업 디렉토리 이동
mkdir -p /apps/container/docker-laravel-development-environment
cd /apps/container/docker-laravel-development-environment
라라벨 개발 환경에 필요한 파일 생성
Docker Compose 파일
더보기
---
cat > docker-compose.yml << 'EOF'
# docker-compose.yml
services:
nginx:
image: nginx:1.29-alpine
container_name: nginx
hostname: nginx
volumes:
- ./html:/var/www/html
- ./docker/nginx/conf.d:/etc/nginx/conf.d
depends_on:
- php-fpm
ports:
- "80:80"
networks:
- laravel-network
php-fpm:
build:
context: .
dockerfile: docker/php/Dockerfile
container_name: php-fpm
hostname: php-fpm
environment:
- DB_HOST=mysql
- DB_DATABASE=laravel
- DB_USERNAME=laravel
- DB_PASSWORD=secret
volumes:
- ./html:/var/www/html
- ./docker/php/php.ini:/usr/local/etc/php/conf.d/custom.ini
networks:
- laravel-network
healthcheck:
test: ["CMD", "php", "-r", "echo 'OK';"]
interval: 30s
timeout: 10s
retries: 3
mysql:
image: mysql:8.4
container_name: mysql
hostname: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
ports:
- "3306:3306"
networks:
- laravel-network
networks:
laravel-network:
driver: bridge
EOF
---
PHP-FPM Dockerfile
더보기
---
cat > docker/php/Dockerfile << 'EOF'
# docker/php/Dockerfile
FROM php:8.3-fpm-alpine
# 1. 시스템 패키지 설치
RUN apk update && apk add --no-cache \
# 기본 도구
curl git unzip vim \
# PHP 확장 의존성
libpng-dev libjpeg-turbo-dev libwebp-dev freetype-dev \
libxml2-dev oniguruma-dev libzip-dev postgresql-dev \
# Redis 빌드 도구(일시적)
autoconf g++ make
# 2. PHP 확장 설치
RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp && \
docker-php-ext-install -j$(nproc) \
pdo_mysql mbstring xml zip bcmath pcntl gd
# 3. Redis 확장 설치
RUN pecl install redis && docker-php-ext-enable redis
# 4. 빌드 도구 정리 (이미지 용량 최적화)
RUN apk del autoconf g++ make && \
rm -rf /tmp/pear && \
docker-php-source delete
# 5. Composer 설치
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# 6. 작업 디렉토리 및 권한 설정
WORKDIR /var/www/html
# 7.권한 설정
RUN chmod -R 775 /var/www/html
# PHP-FPM 사용자 생성 및 권한 설정
#RUN chown -R www-data:www-data /var/www/html
#USER www-data
# 8. health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost/ping || exit 1
EOF
---
PHP 설정 파일
더보기
---
cat > docker/php/php.ini << 'EOF'
# docker/php/php.ini
memory_limit = 512M
upload_max_filesize = 100M
post_max_size = 100M
max_execution_time = 300
max_input_time = 300
; 에러 리포트
display_errors = On
error_reporting = E_ALL
; OPcache (성능 향상)
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
EOF
---
Nginx 설정
더보기
---
cat > docker/nginx/nginx.conf << 'EOF'
# docker/nginx/nginx.conf
user www-data www-data;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
server_tokens off;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
}
EOF
---
Nginx 가상 호스트 설정
더보기
---
cat > docker/nginx/conf.d/default.conf << 'EOF'
# docker/nginx/conf.d/default.conf
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
error_log /var/log/nginx/default-error.log;
access_log /var/log/nginx/default-access.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php-fpm:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
}
location ~ /\.ht {
deny all;
}
}
EOF
---
Docker 컨테이너 빌드 및 실행
docker compose down -v
docker compose build --no-cache php-fpm
docker compose up -d
Nginx 정보 확인
docker compose exec nginx nginx -v
PHP 정보 확인
docker compose exec php-fpm php -v
docker compose exec php-fpm sh -c "php -m | egrep 'redis|gd'"
Nginx 테스트
docker compose exec nginx mkdir -p /var/www/html/public
docker compose exec nginx sh -c "echo 'Container Nginx Server' > /var/www/html/public/index.html"
Nginx 기본 페이지 접속 테스트
curl -I http://localhost
라라벨 프로젝트 생성
public 디렉토리 삭제
docker compose exec nginx rm -rf /var/www/html/public
라라벨 프로젝트 생성
docker compose exec php-fpm composer create-project laravel/laravel . --prefer-dist --no-interaction
라라벨 파일 확인
docker compose exec php-fpm ls -la /var/www/html
환경 설정
docker compose exec php-fpm cp .env.example .env
.env 편집
vim html/.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_CLIENT=phpredis
REDIS_CLUSTER=false
REDIS_HOST=192.168.0.110
REDIS_PORT=6379
REDIS_PASSWORD=redis_password
REDIS_DB=0
APP_KEY 생성
docker compose exec php-fpm php artisan key:generate
APP_KEY 확인
docker compose exec php-fpm grep APP_KEY .env
스토리지 링크 및 권한 설정
- 저장소 권한 설정
docker compose exec php-fpm chmod -R 775 storage bootstrap/cache
- 소유권 설정
docker compose exec php-fpm chown -R www-data:www-data storage
docker compose exec php-fpm chown -R www-data:www-data bootstrap/cache
docker compose exec php-fpm php artisan migrate
캐시 클리어
docker compose exec php-fpm php artisan config:clear
docker compose exec php-fpm php artisan cache:clear
페이지 접속 테스트
curl -s -o /dev/null -w "%{http_code}\n" -I http://localhost
Hello World 페이지 접속 테스트
라우트 파일 생성
docker compose exec php-fpm sh -c 'cat > routes/web.php << "EOF"
<?php
Route::get("/", function() { return "Laravel, Hello World!"; });
EOF'
파일 문법 검사
docker compose exec php-fpm php -l routes/web.php
라우트 목록 확인
docker compose exec php-fpm php artisan route:list
페이지 접속 테스트
curl http://localhost
Laravel, Hello World!
routes/web.php 생성
더보기
---
docker compose exec php-fpm sh -c 'cat > routes/web.php << "EOF"
<?php
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
Route::get("/", function () {
return response()->json([
"message" => "Laravel with MySQL + Redis",
"status" => "success",
"timestamp" => now()->toDateTimeString()
]);
});
// MySQL + Redis 통합 테스트
Route::get("/test-integrated", function () {
$results = [];
try {
// 1. MySQL 연결 테스트
DB::connection()->getPdo();
$results["mysql"] = "✅ MySQL 연결 성공";
// 2. Redis 연결 테스트
Redis::ping();
$results["redis"] = "✅ Redis 연결 성공";
// 3. MySQL에 사용자 데이터 저장
Schema::dropIfExists("test_users");
Schema::create("test_users", function ($table) {
$table->id();
$table->string("name");
$table->string("email")->unique();
$table->integer("login_count")->default(0);
$table->timestamps();
});
$userId = DB::table("test_users")->insertGetId([
"name" => "통합테스트사용자",
"email" => "integrated@" . now()->format("His") . ".com",
"login_count" => 0,
"created_at" => now(),
"updated_at" => now()
]);
$results["mysql_write"] = "✅ MySQL 데이터 저장 (ID: " . $userId . ")";
// 4. Redis에 사용자 캐시 저장
$userKey = "user:" . $userId;
Redis::hset($userKey, "name", "통합테스트사용자");
Redis::hset($userKey, "email", "integrated@" . now()->format("His") . ".com");
Redis::hset($userKey, "last_login", now()->toDateTimeString());
Redis::expire($userKey, 300); // 5분 TTL
$results["redis_write"] = "✅ Redis 사용자 캐시 저장";
// 5. Redis에서 사용자 정보 읽기
$cachedUser = Redis::hgetall($userKey);
$results["redis_read"] = $cachedUser ? "✅ Redis 캐시 읽기 성공" : "❌ Redis 캐시 읽기 실패";
$results["cached_user"] = $cachedUser;
// 6. MySQL에서 데이터 읽기
$dbUser = DB::table("test_users")->where("id", $userId)->first();
$results["mysql_read"] = $dbUser ? "✅ MySQL 데이터 읽기 성공" : "❌ MySQL 데이터 읽기 실패";
// 7. 로그인 카운트 업데이트 (MySQL + Redis 동기화)
DB::table("test_users")->where("id", $userId)->increment("login_count");
Redis::hset($userKey, "login_count",
DB::table("test_users")->where("id", $userId)->value("login_count")
);
$results["sync"] = "✅ MySQL-Redis 데이터 동기화";
// 8. 캐시 시스템 테스트
$cacheKey = "user_profile:" . $userId;
$cachedProfile = Cache::remember($cacheKey, 60, function () use ($userId) {
return DB::table("test_users")->where("id", $userId)->first();
});
$results["cache_system"] = "✅ 라라벨 캐시 시스템 작동";
// 9. 세션 테스트 (Redis 사용)
session(["user_id" => $userId, "last_activity" => now()]);
$results["session"] = "✅ Redis 세션 저장";
$results["status"] = "success";
$results["timestamp"] = now()->toDateTimeString();
} catch (Exception $e) {
$results["status"] = "error";
$results["message"] = $e->getMessage();
$results["timestamp"] = now()->toDateTimeString();
}
// 정리
try {
Schema::dropIfExists("test_users");
Redis::del("user:" . $userId);
$results["cleanup"] = "✅ 테스트 데이터 정리 완료";
} catch (Exception $e) {
$results["cleanup"] = "⚠️ 정리 실패: " . $e->getMessage();
}
return response()->json($results);
});
// 캐시 성능 테스트
Route::get("/test-cache-performance", function () {
$results = [];
$iterations = 10;
// MySQL 직접 조회 시간 측정
$startTime = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
DB::select("SELECT SLEEP(0.01)"); // 작은 지연 시뮬레이션
}
$mysqlTime = microtime(true) - $startTime;
// Redis 조회 시간 측정
$startTime = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
Redis::get("test_key_" . $i);
}
$redisTime = microtime(true) - $startTime;
// 캐시 시스템 시간 측정
$startTime = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
Cache::get("test_key_" . $i);
}
$cacheTime = microtime(true) - $startTime;
$results["mysql_time"] = round($mysqlTime, 4) . "초";
$results["redis_time"] = round($redisTime, 4) . "초";
$results["cache_time"] = round($cacheTime, 4) . "초";
$results["iterations"] = $iterations;
$results["performance_ratio"] = round($mysqlTime / $redisTime, 2) . "x faster";
return response()->json($results);
});
// 실전 예제: 사용자 프로필 시스템
Route::get("/user-profile/{id}", function ($id) {
$cacheKey = "user_profile_" . $id;
// 캐시에서 사용자 프로필 가져오기 (없으면 MySQL에서 조회)
$userProfile = Cache::remember($cacheKey, 300, function () use ($id) {
$user = DB::table("users")->where("id", $id)->first();
if ($user) {
// 추가 데이터 처리 (가정)
return [
"id" => $user->id,
"name" => $user->name,
"email" => $user->email,
"cached_at" => now()->toDateTimeString(),
"source" => "database"
];
}
return null;
});
if (!$userProfile) {
return response()->json([
"status" => "error",
"message" => "User not found"
], 404);
}
// 접근 통계를 Redis에 저장
Redis::zincrby("user_access_stats", 1, $id);
Redis::hset("user_last_access", $id, now()->toDateTimeString());
return response()->json([
"status" => "success",
"data" => $userProfile,
"access_stats" => [
"rank" => Redis::zrevrank("user_access_stats", $id),
"score" => Redis::zscore("user_access_stats", $id)
]
]);
});
// 대기열(Queue) 테스트
Route::get("/test-queue", function () {
// 이메일 발송 작업을 대기열에 추가 (Redis 사용)
$jobId = Illuminate\Support\Facades\Queue::push(new class implements Illuminate\Contracts\Queue\ShouldQueue {
public function handle() {
// 실제 작업 시뮬레이션
Log::info("Queue job executed at: " . now()->toDateTimeString());
Redis::incr("queue_job_count");
}
});
return response()->json([
"status" => "success",
"message" => "Job added to queue",
"job_id" => $jobId,
"queue_driver" => config("queue.default"),
"pending_jobs" => Redis::llen("queues:default")
]);
});
// 시스템 상태 모니터링
Route::get("/system-status", function () {
$status = [];
try {
// MySQL 상태
$status["mysql"] = [
"connected" => true,
"database" => DB::connection()->getDatabaseName(),
"version" => DB::select("SELECT VERSION() as version")[0]->version,
"tables" => count(DB::select("SHOW TABLES"))
];
} catch (Exception $e) {
$status["mysql"] = ["connected" => false, "error" => $e->getMessage()];
}
try {
// Redis 상태
$status["redis"] = [
"connected" => true,
"ping" => Redis::ping(),
"info" => Redis::info("memory"),
"keys_count" => Redis::dbsize()
];
} catch (Exception $e) {
$status["redis"] = ["connected" => false, "error" => $e->getMessage()];
}
// 캐시 상태
$status["cache"] = [
"driver" => config("cache.default"),
"stores" => array_keys(config("cache.stores"))
];
return response()->json($status);
});
// 사용자 로그인 시뮬레이션
Route::get("/login/{userId}", function ($userId) {
// MySQL에서 사용자 확인 (가정)
$user = DB::table("users")->where("id", $userId)->first();
if (!$user) {
return response()->json(["error" => "User not found"], 404);
}
// Redis에 세션 저장
$sessionId = Str::random(40);
Redis::setex("session:" . $sessionId, 3600, json_encode([
"user_id" => $user->id,
"name" => $user->name,
"email" => $user->email,
"login_time" => now()->toDateTimeString(),
"ip" => request()->ip()
]));
// 활성 세션 목록 업데이트
Redis::sadd("active_sessions", $sessionId);
return response()->json([
"status" => "success",
"message" => "Login successful",
"session_id" => $sessionId,
"user" => [
"id" => $user->id,
"name" => $user->name
]
]);
});
// 세션 확인
Route::get("/profile", function () {
$sessionId = request()->header("X-Session-ID");
if (!$sessionId) {
return response()->json(["error" => "Session required"], 401);
}
$sessionData = Redis::get("session:" . $sessionId);
if (!$sessionData) {
return response()->json(["error" => "Invalid session"], 401);
}
$userData = json_decode($sessionData, true);
// 세션 연장
Redis::expire("session:" . $sessionId, 3600);
return response()->json([
"status" => "success",
"user" => $userData,
"active_sessions_count" => Redis::scard("active_sessions")
]);
});
// 실시간 통계
Route::get("/stats", function () {
$stats = [];
// MySQL 통계
$stats["mysql"] = [
"total_users" => DB::table("users")->count(),
"tables" => count(DB::select("SHOW TABLES"))
];
// Redis 통계
$stats["redis"] = [
"total_keys" => Redis::dbsize(),
"active_sessions" => Redis::scard("active_sessions"),
"memory_usage" => Redis::info("memory")["used_memory_human"]
];
return response()->json($stats);
});
EOF'
---
PHP 문법 검사
docker compose exec php-fpm php -l routes/web.php
라우트 캐시 클리어
docker compose exec php-fpm php artisan route:clear
라우트 목록 확인
docker compose exec php-fpm php artisan route:list
테스트
curl -s http://localhost/system-status | jq .
curl -s http://localhost/test-integrated | jq .
728x90
반응형