TP12.1 - Dockerisation Complète
أهداف التمرين
في هذا التمرين، سوف تقوم بـ:
- ✅ إنشاء Dockerfile متعدد المراحل ومحسن
- ✅ تكوين Docker Compose مع PostgreSQL
- ✅ إدارة ملفات التعريف والتكوينات
- ✅ اختبار التطبيق في الحاوية
المدة المقدرة: 1 ساعة و30 دقيقة
السياق
أنت تقوم بتحويل تطبيق إدارة المهام (Todo App) إلى حاوية باستخدام قاعدة بيانات PostgreSQL.
الجزء 1: هيكل المشروع
todo-app/
├── src/
│ └── main/
│ ├── java/com/example/todo/
│ │ ├── TodoApplication.java
│ │ ├── entity/Todo.java
│ │ ├── repository/TodoRepository.java
│ │ ├── service/TodoService.java
│ │ └── controller/TodoController.java
│ └── resources/
│ ├── application.properties
│ ├── application-dev.properties
│ ├── application-docker.properties
│ └── application-prod.properties
├── pom.xml
├── Dockerfile
├── docker-compose.yml
├── docker-compose.prod.yml
└── .dockerignore
الجزء 2: التطبيق
كيان Todo
@Entity
@Table(name = "todos")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String title;
private String description;
private boolean completed = false;
@Column(name = "created_at")
private LocalDateTime createdAt = LocalDateTime.now();
}
TodoController
@RestController
@RequestMapping("/api/todos")
@RequiredArgsConstructor
public class TodoController {
private final TodoService todoService;
@GetMapping
public List<Todo> getAll() {
return todoService.findAll();
}
@GetMapping("/{id}")
public Todo getById(@PathVariable Long id) {
return todoService.findById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Todo create(@Valid @RequestBody Todo todo) {
return todoService.create(todo);
}
@PutMapping("/{id}")
public Todo update(@PathVariable Long id, @Valid @RequestBody Todo todo) {
return todoService.update(id, todo);
}
@PatchMapping("/{id}/toggle")
public Todo toggle(@PathVariable Long id) {
return todoService.toggleComplete(id);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
todoService.delete(id);
}
}
الجزء 3: ملفات التكوين
application.properties
spring.application.name=todo-app
# الملف الشخصي الافتراضي
spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev}
# Actuator
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=always
application-dev.properties
# H2 للتطوير المحلي
spring.datasource.url=jdbc:h2:mem:tododb
spring.datasource.driver-class-name=org.h2.Driver
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
logging.level.com.example=DEBUG
application-docker.properties
# PostgreSQL عبر Docker Compose
spring.datasource.url=jdbc:postgresql://db:5432/tododb
spring.datasource.username=todouser
spring.datasource.password=todopass
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
logging.level.com.example=INFO
application-prod.properties
# الإنتاج - يستخدم متغيرات البيئة
spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
logging.level.root=WARN
logging.level.com.example=INFO
# الأمان
server.error.include-stacktrace=never
management.endpoints.web.exposure.include=health,info,prometheus
الجزء 4: Dockerfile
Dockerfile
# ===== STAGE 1: Build =====
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
# Copier pom.xml et télécharger les dépendances (cache)
COPY pom.xml .
RUN mvn dependency:go-offline -B
# Copier le code source
COPY src ./src
# Compiler l'application
RUN mvn clean package -DskipTests
# ===== STAGE 2: Runtime =====
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Créer un utilisateur non-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Créer le répertoire des logs
RUN mkdir -p /app/logs && chown -R appuser:appgroup /app
# Passer à l'utilisateur non-root
USER appuser:appgroup
# Copier le JAR depuis le builder
COPY --from=builder /app/target/*.jar app.jar
# Exposer le port
EXPOSE 8080
# Variables d'environnement par défaut
ENV JAVA_OPTS="-Xms256m -Xmx512m"
ENV SPRING_PROFILES_ACTIVE="docker"
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8080/actuator/health || exit 1
# Lancer l'application
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
الجزء 5: Docker Compose
docker-compose.yml (التطوير)
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: todo-app
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
db:
condition: service_healthy
networks:
- todo-network
restart: unless-stopped
db:
image: postgres:15-alpine
container_name: todo-db
environment:
POSTGRES_DB: tododb
POSTGRES_USER: todouser
POSTGRES_PASSWORD: todopass
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U todouser -d tododb"]
interval: 10s
timeout: 5s
retries: 5
networks:
- todo-network
# اختياري: PgAdmin لإدارة قاعدة البيانات
pgadmin:
image: dpage/pgadmin4:latest
container_name: todo-pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
depends_on:
- db
networks:
- todo-network
volumes:
postgres_data:
networks:
todo-network:
driver: bridge
docker-compose.prod.yml
version: '3.8'
services:
app:
image: ${DOCKER_IMAGE:-todo-app:latest}
container_name: todo-app-prod
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DATABASE_URL=${DATABASE_URL}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- JAVA_OPTS=-Xms512m -Xmx1024m
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
restart: always
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
الجزء 6: .dockerignore
# Build output
target/
!target/*.jar
# IDE
.idea/
*.iml
.vscode/
*.swp
# Git
.git/
.gitignore
# Docker files
Dockerfile*
docker-compose*.yml
.dockerignore
# Logs
*.log
logs/
# Tests
src/test/
# Documentation
*.md
docs/
# Secrets (ne jamais les inclure!)
.env
*.pem
الجزء 7: الاختبارات
البناء والتشغيل
# 1. بناء الصورة
docker-compose build
# 2. بدء الخدمات
docker-compose up -d
# 3. التحقق من السجلات
docker-compose logs -f app
# 4. التحقق من الصحة
curl http://localhost:8080/actuator/health
اختبار API
# إنشاء مهمة
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title": "تعلم Docker", "description": "تحويل Spring Boot إلى حاوية"}'
# قائمة المهام
curl http://localhost:8080/api/todos
# تبديل الاكتمال
curl -X PATCH http://localhost:8080/api/todos/1/toggle
# حذف
curl -X DELETE http://localhost:8080/api/todos/1
الوصول إلى PgAdmin
- افتح http://localhost:5050
- تسجيل الدخول: admin@example.com / admin
- إضافة خادم:
- المضيف: db
- المنفذ: 5432
- اسم المستخدم: todouser
- كلمة المرور: todopass
الجزء 8: أوامر مفيدة
# عرض الحاويات
docker-compose ps
# عرض السجلات
docker-compose logs -f
# إيقاف كل شيء
docker-compose down
# إيقاف وحذف الأحجام
docker-compose down -v
# إعادة البناء بدون ذاكرة التخزين المؤقت
docker-compose build --no-cache
# تنفيذ shell في الحاوية
docker exec -it todo-app sh
# عرض حجم الصور
docker images | grep todo
التحقق النهائي
قائمة التحقق
-
docker-compose upيبدأ بدون أخطاء - التطبيق يستجيب على http://localhost:8080
-
/actuator/healthيعيد UP - قاعدة بيانات PostgreSQL متصلة
- المهام محفوظة (إعادة التشغيل OK)
- الصورة أقل من 300 ميجابايت
التحقق من حجم الصورة
docker images todo-app
# REPOSITORY TAG SIZE
# todo-app latest ~180MB
🎯 الخطوة التالية
➡️ TP12.2: خط أنابيب CI/CD والنشر