Containerizing a full-stack application can be challenging—especially when you want to keep builds efficient, secure, and optimized for production. In this guide, we’ll walk through how to dockerize a Spring Boot backend, Angular frontend, and MySQL database using multi-stage Docker builds and Docker Compose.
🧠 Why Multi-Stage Docker Builds?
Traditional Docker builds bundle everything—including build tools and dev dependencies—into the final image. This results in:
❌ Large image sizes
❌ Slower deployments
❌ Unnecessary security exposure
Multi-Stage Builds solve these issues by:
✅ Separating build and runtime environments
✅ Producing smaller, optimized final images
✅ Improving image security
🎨 Dockerizing the Angular Frontend
We use a two-stage Dockerfile:
# Build stage
FROM node:lts-slim AS build
WORKDIR /src
COPY package*.json ./
RUN npm ci
COPY . ./
RUN npm run build -- --configuration=production --output-path=dist
# Production stage
FROM nginx:stable AS final
EXPOSE 4200
RUN rm -f /usr/share/nginx/html/index.html
COPY --from=build /src/dist/browser /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
This approach ensures the production image contains only the compiled Angular assets and nginx, without the Node.js build tools.
nginx.conf (runs frontend on port 4200):
events { }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 4200;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html;
}
}
}
✅ Final image contains only compiled Angular + Nginx, not Node.js.
🏗️ Dockerizing the Spring Boot Backend
We again use a multi-stage approach:
# Build Stage
FROM eclipse-temurin:17-jdk-jammy AS builder
WORKDIR /app
COPY mvnw .
COPY .mvn/ .mvn/
COPY pom.xml .
COPY src/ src/
RUN ./mvnw clean package -DskipTests
# Runtime Stage
FROM eclipse-temurin:17-jre-jammy AS runtime
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN groupadd -r spring && useradd -r -g spring spring
USER spring
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 9090
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
application.properties
spring.application.name=projectassignment
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/projectassignment?allowPublicKeyRetrieval=true&useSSL=false
spring.datasource.username=${MYSQL_USERNAME:root}
spring.datasource.password=${MYSQL_PASSWORD:root}
spring.jpa.hibernate.ddl-auto=${MYSQL_STRATEGY:update}
server.port=${SERVER_PORT:9090}
🔁 Notice: MYSQL_HOST will be overridden by Docker Compose.
🧱 Docker Compose: Orchestrating All Services
services:
mysql-db:
image: mysql:8.0
container_name: mysql-db
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: projectassignment
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-proot"]
interval: 10s
timeout: 5s
retries: 5
spring-backend:
build:
context: ./project_assignment(backend)
dockerfile: Dockerfile
image: pabackend
container_name: spring-backend
ports:
- "9090:9090"
environment:
- MYSQL_HOST=mysql-db
depends_on:
mysql-db:
condition: service_healthy
restart: unless-stopped
angular-frontend:
build:
context: ./project_assignment(frontend)
dockerfile: Dockerfile
image: pafrontend
container_name: angular-frontend
ports:
- "4200:4200"
depends_on:
- spring-backend
restart: unless-stopped
🌐 Nginx Reverse Proxy
If you want all traffic via port 80:
server {
listen 80;
server_name _;
location / {
proxy_pass http://localhost:4200;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_redirect http://localhost:4200/ http://$host/;
}
location /api/ {
proxy_pass http://localhost:9090/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
✅ Final Result
After running:
docker compose up --build -d
You will have:
| Service | URL |
|---|---|
| Frontend | http://localhost:4200 |
| Backend API | http://localhost:9090 |
| Database | localhost:3306 |
🎯 Summary
Using multi-stage builds + Docker Compose makes your application:
- Faster to build
- Smaller to deploy
- Easier to maintain
- More secure in production


Top comments (0)