This is a real-world story — with challenges, solutions, and valuable lessons that may benefit other developers or serve as a reference for myself in the future.
Introduction
The project is a web application built with Spring Boot and Angular. Initially, I went with a simple monolithic architecture, but as the requirements and ambitions grew, I had to switch to a microservices-based structure. Here's how it all happened — including configurations, issues, and solutions.
Monolithic Architecture
Initial setup:
- Backend: Spring Boot
- Frontend: Angular I already had a rented VDS, so I registered a domain via a well-known provider and decided to host the site on that VDS. To keep things simple, I integrated the Angular build into the Spring Boot resources using frontend-maven-plugin, bundling everything into a single JAR file.
Why? I had done it before and it worked, so I chose the familiar path.
After registering the domain (which takes some time to propagate), I began configuring and testing the project. But during testing, major drawbacks surfaced:
- No SSR in Angular → SEO was negatively impacted
- Fragile build: Angular errors broke Maven build
- Maintenance and scalability became difficult
Moving to Microservices
I decided to split the frontend and backend into separate services and introduce Angular Universal for SSR. Here's the updated architecture:
- Backend: Spring Boot
- Frontend: Angular Universal (SSR)
- Infrastructure: Docker Compose, Nginx, HTTPS
Docker, Nginx and Initial Launch
Base docker-compose.yml setup:
services:
frontend-service:
image: node:18-alpine
command: node server/main.js
backend-service:
image: amazoncorretto:17-alpine-jdk
command: java -jar app.jar
nginx:
image: nginx:alpine
# Proxy configuration
Initial Nginx config:
server {
server_name my-site.ru;
location / {
proxy_pass http://frontend-service:9002;
}
location /api {
proxy_pass http://backend-service:9001;
}
}
The site started and worked via IP, but there were many issues:
- Duplicated API prefixes (/api/api/endpoint) → Fixed proxy_pass and Angular API base URL
- Redirects on POST requests due to Spring adding extra slashes → Fixed via correct controller annotations and Nginx tuning
- 405 Method Not Allowed → Fixed by explicitly using @PostMapping
- Docker networking issues → Resolved by defining a shared network in docker-compose.yml
Security: HTTPS, SSL, CORS
To make the site accessible via domain name, I configured DNS records through my provider (they offer free DNS). I pointed @ and www to the VDS IP address.
The domain provider also offered free SSL certificates (Let's Encrypt). Here's how I set them up: downloaded from the provider panel:
- domain.crt — certificate
- domain.key — private key
- ca_bundle.crt — certificate chain
Nginx HTTPS configuration:
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/domain.crt;
ssl_certificate_key /etc/nginx/ssl/domain.key;
ssl_trusted_certificate /etc/nginx/ssl/ca_bundle.crt;
However, HTTPS failed to work initially. The Nginx logs showed SSL_CTX_use_PrivateKey_file
errors. The issue? The certificate was in PKCS#7 format, but Nginx requires PEM. I converted it using OpenSSL:
openssl pkcs7 -print_certs -in domain.p7b -out domain.crt
Also, since certificates expire, don't forget to renew them. Eventually, I plan to automate this (e.g. with Certbot).
On the Spring Boot side, CORS errors were solved like this:
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://example.com");
}
};
}
Bot Attacks and Protection
Soon after launching the site, Nginx logs filled with automated bot requests probing for WordPress, PHPMyAdmin, etc. Example:
45.155.205.213 - - [01/Jan/2023:04:12:11 +0000] "GET /wp-login.php HTTP/1.1" 404 153
Nginx protection rules:
location ~* ^/(wp-admin|wp-login|phpmyadmin|.env|config.php) {
return 403;
}
if ($request_method ~ ^(TRACE|TRACK|DEBUG)) {
return 405;
}
Final Configuration
After several iterations, here’s what my current setup looks like:
docker-compose.yml
version: '3.8'
networks:
app-network:
services:
backend-service:
build:
context: ./backend
dockerfile: Dockerfile
container_name: backend-service
ports:
- "9001:9001"
volumes:
- ./backend/logs:/app/logs
env_file:
- ./.env
restart: always
networks:
- app-network
frontend-service:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: frontend-service
ports:
- "9002:9002"
environment:
- NODE_ENV=production
restart: always
networks:
- app-network
proxy-service:
image: nginx:alpine
container_name: proxy-service
volumes:
- ./nginx/conf:/etc/nginx/conf.d:ro
- ./ssl-certs:/etc/nginx/ssl:ro
- ./nginx/logs:/var/log/nginx
ports:
- "80:80"
- "443:443"
depends_on:
- backend-service
- frontend-service
restart: always
networks:
- app-network
nginx.conf
worker_processes auto;
events {
worker_connections 1024;
}
http {
upstream frontend {
server frontend-service:9002;
}
upstream backend {
server backend-service:9001;
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/ {
proxy_pass http://backend/api/;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location ~* ^/(wp-admin|wp-login|phpmyadmin|.env|config.php) {
return 403;
}
if ($request_method ~ ^(TRACE|TRACK|DEBUG)) {
return 405;
}
}
}
backend Dockerfile
FROM amazoncorretto:17-alpine-jdk
WORKDIR /app
COPY my-spring-1.0.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
frontend Dockerfile
FROM node:18-alpine AS runtime
WORKDIR /app
COPY browser /app/browser
COPY server /app/server
COPY node_modules /app/node_modules
EXPOSE 4000
CMD ["node", "/app/server/server.mjs"]
Conclusion
The website is now fully up and running. Splitting services made it more maintainable and scalable. Hopefully, this post will help others going down a similar path with Java and Angular.
Top comments (0)