Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.git
.github
.gradle
build/
out/
node_modules/
src/main/webapp/node_modules/
src/main/webapp/dist/
*.iml
.idea/
Dockerfile
docker-compose.yml
docker/postgres_data/
.DS_Store
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,31 @@ Harmonia is built as a Java/Spring-based monolith, prioritizing stability and jo

---

## 🐳 Docker Command for PostgreSQL
## 🐳 Run Harmonia with Docker

Use this command to quickly set up a local instance of the PostgreSQL database required by the server.
### Full stack (database + server + client)

1. Build and launch everything:

```bash
docker compose -f docker/docker-compose.yml up -d
docker compose -f docker/docker-compose.yml up --build
```

3. Access the services:
* Client (served by nginx): http://localhost:5173
* Spring Boot server: http://localhost:8080
* PostgreSQL: localhost:5432 (user `postgres`, password `harmonia`)

The Compose setup builds the Gradle boot jar inside `docker/server.Dockerfile`, bundles the React client with Vite via `docker/client.Dockerfile`, and proxies `/api` + `/actuator` calls from nginx to the server container. All images restart automatically unless stopped.

### Database only

If you only need the database for local development you can start just the PostgreSQL service:

```bash
docker compose -f docker/docker-compose.yml up -d postgres
```

## 🚀 How to Run the Server

The application uses Gradle for dependency management and building. Assuming you have **Java 25** installed, use the
Expand Down Expand Up @@ -129,4 +146,4 @@ This will generate fully typed API clients and models in:

src/main/webapp/src/app/generated/

These files are overwritten each time you run the generator and should be committed to version control to keep client and server in sync.
These files are overwritten each time you run the generator and should be committed to version control to keep client and server in sync.
24 changes: 24 additions & 0 deletions docker/client.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Build the React/Vite client
FROM node:22-bookworm-slim AS builder
WORKDIR /app

COPY src/main/webapp/package*.json ./
RUN npm ci

COPY src/main/webapp .

ARG VITE_API_BASE_URL=http://localhost:8080
ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
ENV NODE_ENV=production

RUN npm run build

# Serve the production build with nginx
FROM nginx:1.27-alpine AS runner

COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
41 changes: 40 additions & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
version: '3.9'
name: harmonia

services:
postgres:
image: postgres:18.0
Expand All @@ -12,6 +13,44 @@ services:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

server:
build:
context: ..
dockerfile: docker/server.Dockerfile
container_name: harmonia-server
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/harmonia
SPRING_DATASOURCE_USERNAME: postgres
SPRING_DATASOURCE_PASSWORD: harmonia
SPRING_AI_AZURE_OPENAI_API_KEY: ${SPRING_AI_AZURE_OPENAI_API_KEY:-change-me}
SPRING_AI_AZURE_OPENAI_ENDPOINT: ${SPRING_AI_AZURE_OPENAI_ENDPOINT:-https://ase-se01.openai.azure.com/}
SPRING_AI_AZURE_OPENAI_CHAT_OPTIONS_DEPLOYMENT_NAME: ${SPRING_AI_AZURE_OPENAI_CHAT_OPTIONS_DEPLOYMENT_NAME:-gpt-5-mini}
ports:
- "8080:8080"

client:
build:
context: ..
dockerfile: docker/client.Dockerfile
args:
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://localhost:8080}
container_name: harmonia-client
restart: unless-stopped
depends_on:
server:
condition: service_started
ports:
- "5173:80"

volumes:
postgres_data:
30 changes: 30 additions & 0 deletions docker/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
server {
listen 80;
server_name _;

root /usr/share/nginx/html;
index index.html;

gzip on;
gzip_types text/plain application/xml text/css application/javascript application/json image/svg+xml;

location / {
try_files $uri /index.html;
}

location /api/ {
proxy_pass http://harmonia-server:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /actuator/ {
proxy_pass http://harmonia-server:8080/actuator/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
20 changes: 20 additions & 0 deletions docker/server.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Build the Spring Boot application
FROM eclipse-temurin:25-jdk AS builder
WORKDIR /app

COPY . .

RUN chmod +x gradlew
RUN ./gradlew --no-daemon bootJar -x test \
&& JAR_FILE=$(find build/libs -maxdepth 1 -type f -name "*.jar" ! -name "*-plain.jar" | head -n 1) \
&& cp "${JAR_FILE}" /app/application.jar

# Run the packaged jar with a lightweight JRE
FROM eclipse-temurin:25-jre
WORKDIR /app

COPY --from=builder /app/application.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "/app/app.jar"]
119 changes: 119 additions & 0 deletions start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/bin/bash

# Harmonia Startup Script
# This script starts all services using Docker containers

set -e # Exit on error

# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color

echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Starting Harmonia Application${NC}"
echo -e "${BLUE} (Containerized Version)${NC}"
echo -e "${BLUE}========================================${NC}\n"

# Step 1: Check Docker is running
echo -e "${GREEN}[1/4] Checking Docker...${NC}"
if ! docker info >/dev/null 2>&1; then
echo -e "${RED}✗ Docker is not running. Please start Docker Desktop.${NC}"
exit 1
fi
echo -e "${GREEN}✓ Docker is running${NC}\n"

# Step 2: Clean up any existing containers
echo -e "${GREEN}[2/4] Cleaning up existing containers...${NC}"
docker compose -f docker/docker-compose.yml down --remove-orphans 2>/dev/null || true
echo -e "${GREEN}✓ Cleanup completed${NC}\n"

# Step 3: Build and start all services
echo -e "${GREEN}[3/4] Building and starting all services...${NC}"
echo -e "${YELLOW}This may take several minutes for the first run...${NC}"
echo -e "${YELLOW}Building Docker images and starting containers...${NC}\n"

# Start services in background and capture output
docker compose -f docker/docker-compose.yml up --build -d

if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ All services started successfully${NC}\n"
else
echo -e "${RED}✗ Failed to start services${NC}"
echo -e "${YELLOW}Check logs with: docker compose -f docker/docker-compose.yml logs${NC}"
exit 1
fi

# Step 4: Wait for services to be ready
echo -e "${GREEN}[4/4] Waiting for services to be ready...${NC}"
echo -e "${YELLOW}Checking service health...${NC}"

# Wait for postgres to be healthy
echo -e "${YELLOW}• Waiting for PostgreSQL...${NC}"
timeout=60
while [ $timeout -gt 0 ]; do
if docker compose -f docker/docker-compose.yml ps postgres | grep -q "healthy"; then
echo -e "${GREEN} ✓ PostgreSQL is ready${NC}"
break
fi
sleep 2
((timeout-=2))
done

if [ $timeout -le 0 ]; then
echo -e "${RED} ✗ PostgreSQL failed to start within 60 seconds${NC}"
exit 1
fi

# Wait for server to be responding
echo -e "${YELLOW}• Waiting for Spring Boot server...${NC}"
timeout=120
while [ $timeout -gt 0 ]; do
if curl -f -s http://localhost:8080/actuator/health >/dev/null 2>&1; then
echo -e "${GREEN} ✓ Server is ready${NC}"
break
fi
sleep 3
((timeout-=3))
done

if [ $timeout -le 0 ]; then
echo -e "${YELLOW} ⚠ Server may still be starting (timeout reached)${NC}"
fi

# Check if client is responding
echo -e "${YELLOW}• Waiting for React client...${NC}"
timeout=30
while [ $timeout -gt 0 ]; do
if curl -f -s http://localhost:5173 >/dev/null 2>&1; then
echo -e "${GREEN} ✓ Client is ready${NC}"
break
fi
sleep 2
((timeout-=2))
done

if [ $timeout -le 0 ]; then
echo -e "${YELLOW} ⚠ Client may still be starting (timeout reached)${NC}"
fi

# Summary
echo -e "\n${BLUE}========================================${NC}"
echo -e "${GREEN}✓ Harmonia is now running!${NC}"
echo -e "${BLUE}========================================${NC}"
echo -e "${YELLOW}Server:${NC} http://localhost:8080"
echo -e "${YELLOW}Client:${NC} http://localhost:5173"
echo -e "${YELLOW}Database:${NC} PostgreSQL on localhost:5432"
echo -e "\n${YELLOW}Useful commands:${NC}"
echo -e "${YELLOW}• View logs:${NC} docker compose -f docker/docker-compose.yml logs -f"
echo -e "${YELLOW}• Stop services:${NC} docker compose -f docker/docker-compose.yml down"
echo -e "${YELLOW}• Restart services:${NC} docker compose -f docker/docker-compose.yml restart"
echo -e "\n${YELLOW}Press Ctrl+C to stop all services${NC}\n"

# Keep script running and handle Ctrl+C
trap "echo -e '\n${RED}Shutting down all services...${NC}'; docker compose -f docker/docker-compose.yml down; exit" INT

# Follow logs to keep script running
docker compose -f docker/docker-compose.yml logs -f
Loading