Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
35 changes: 35 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# === Build artifacts ===
target/
!target/*.jar

# === Dashboard build deps (handled inside Docker) ===
eventara-dashboard/node_modules/
eventara-dashboard/dist/

# === IDE / editor ===
.idea/
*.iml
*.iws
.vscode/
*.swp
*.swo
*~

# === OS junk ===
.DS_Store
Thumbs.db

# === Git ===
.git/
.gitignore

# === Docs & non-essential ===
ai-docs/
*.md
LICENSE

# === Docker (don't send compose files into the build) ===
docker-compose*.yaml
.env
.env.example
Dockerfile
10 changes: 8 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ POSTGRES_PASSWORD=mysecretpassword
POSTGRES_DB=eventara

# Required for Kafka KRaft mode. Do not change after first boot unless you wipe the kafka volume.
KAFKA_KRAFT_CLUSTER_ID=NvDmnaWzQgiH8qbnraqxcg
CLUSTER_ID=NvDmnaWzQgiH8qbnraqxcg

Comment on lines 5 to 7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You renamed KAFKA_KRAFT_CLUSTER_IDCLUSTER_ID in .env.example and compose. That’s fine for the new image, but it’s a breaking change for anyone with existing .env files from previous versions. The docs mention stability, but there’s no explicit migration note that users must rename the variable (or they’ll silently fall back to the default).

Given the importance of cluster IDs, silently defaulting could accidentally create a new cluster metadata state when users expected their old one.

Suggestion

Add a short migration note in DEPLOYMENT.md (or README) such as:

  • "If upgrading from older versions, rename KAFKA_KRAFT_CLUSTER_ID to CLUSTER_ID in your .env."
  • Optionally support both temporarily in compose: CLUSTER_ID: ${CLUSTER_ID:-${KAFKA_KRAFT_CLUSTER_ID:-NvDm...}}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit implementing backward-compatible env var fallback plus the doc note.

# Only needed if you want to connect to Kafka from outside Docker.
#
Expand All @@ -14,5 +14,11 @@ KAFKA_KRAFT_CLUSTER_ID=NvDmnaWzQgiH8qbnraqxcg
#
# KAFKA_EXTERNAL_HOST=localhost

# Feature flags
# Host port for the application
# EVENTARA_PORT=8080

# JVM memory (adjust for your VPS RAM)
# JAVA_OPTS=-Xms256m -Xmx512m

# Feature flags (Please keep this as it is: Do not change no matter what)
EVENTARA_METRICS_DISTRIBUTED_ENABLED=true
92 changes: 66 additions & 26 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,57 @@
# Deployment (Docker image)
# Deployment

This guide is the fastest way to run Eventara on a single VM using Docker.
Deploy Eventara on any VPS with Docker.

## Prerequisites

- Docker Engine + Docker Compose plugin installed
- A VM with ~2 CPU / 4 GB RAM is usually fine for a quick demo
- Docker Engine + Docker Compose plugin
- ~2 CPU / 4 GB RAM (minimum)

## Quick deploy (recommended)

1. Clone the repo and create your env file:
## Quick deploy

```bash
git clone https://github.com/tusharkhatriofficial/eventara.git
cd eventara

cp .env.example .env
```
# Edit .env — at minimum change POSTGRES_PASSWORD

2. Start the stack:

```bash
docker compose --env-file .env -f docker-compose.prod.yaml up -d --build
```

3. Open:
The build takes 2-5 minutes. It compiles the React dashboard and Spring Boot app into a **single container** — both API and dashboard are served on port `8080`.

| What | URL |
|---|---|
| Dashboard + API | `http://<server>:8080` |
| Swagger | `http://<server>:8080/swagger-ui.html` |

- API: `http://<server>:8080`
- Swagger: `http://<server>:8080/swagger-ui.html`
## Coolify

Note: If you have a frontend bundle in `src/main/resources/static`, Spring Boot will serve it at `/` (same `:8080`). If you want the Vite dev dashboard instead, use `docker-compose.yaml`.
1. **Add a new resource** → Docker Compose
2. Paste the contents of `docker-compose.prod.yaml` (or point it to the repo)
3. Add the environment variables from `.env.example` in the **Environment Variables** tab
4. Set the exposed port to `8080`
5. Deploy

Spring profile note: the compose files don’t set `SPRING_PROFILES_ACTIVE`, so the app runs with Spring’s default profile. If you need a specific profile, add `SPRING_PROFILES_ACTIVE` to the `eventara` service in `docker-compose.prod.yaml` (or `springboot` in `docker-compose.yaml`).
Coolify will handle the build and TLS for you.

## DigitalOcean / Generic Ubuntu VPS

```bash
# SSH into your droplet
ssh root@<your-ip>

# Install Docker (if not already installed)
curl -fsSL https://get.docker.com | sh

# Clone and deploy
git clone https://github.com/tusharkhatriofficial/eventara.git
cd eventara
cp .env.example .env
nano .env # change POSTGRES_PASSWORD at minimum

docker compose --env-file .env -f docker-compose.prod.yaml up -d --build
```

## Common operations

Expand All @@ -46,17 +66,37 @@ docker compose -f docker-compose.prod.yaml down
docker compose -f docker-compose.prod.yaml down -v
```

## Kafka without Zookeeper (KRaft)
## Resource limits

The prod compose file sets memory limits:

| Service | Memory Limit |
|---|---|
| Postgres | 1 GB |
| Kafka | 1 GB |
| Redis | 512 MB |
| Eventara (API + Dashboard) | 1 GB |

Total: ~3.5 GB. A 4 GB VPS works for demos and light use.

Comment on lines +69 to +81

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prod docs state the compose file "sets memory limits" totaling ~3.5GB. Given deploy.resources often won’t apply in standard Docker Compose (non-Swarm), this section is potentially misleading and could cause operators to size their VPS incorrectly.

This should be aligned with the actual behavior of the compose file.

Suggestion

Clarify how limits are enforced:

  • Add a note like: "deploy.resources is applied in Swarm; for plain docker compose up limits may not be enforced".
  • If you adopt mem_limit (see compose suggestion), update the docs to say limits are enforced by Docker.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit updating DEPLOYMENT.md to accurately describe limit enforcement.

## Environment variables

`docker-compose.yaml` and `docker-compose.prod.yaml` run Kafka in **KRaft mode**, so there is no Zookeeper container.
| Variable | Default | Description |
|---|---|---|
| `POSTGRES_USER` | `postgres` | Database user |
| `POSTGRES_PASSWORD` | `mysecretpassword` | **Change this** |
| `POSTGRES_DB` | `eventara` | Database name |
| `CLUSTER_ID` | `NvDmnaWzQgiH8qbnraqxcg` | Kafka KRaft cluster ID — don't change after first boot |
| `EVENTARA_PORT` | `8080` | Host port for the app |
| `JAVA_OPTS` | `-Xms256m -Xmx512m` | JVM memory settings |

Important: `KAFKA_KRAFT_CLUSTER_ID` must stay stable for a given Kafka data volume. If you change it, wipe the Kafka volume (`docker compose down -v`) before starting again.
## Kafka (KRaft mode)

## Suggested changes for a real production setup
No Zookeeper needed. `CLUSTER_ID` must stay stable for a given Kafka data volume. If you change it, wipe the volume first (`docker compose down -v`).

These aren’t required for a “get it up in a few hours” deployment, but they’re the usual next steps:
## Production hardening checklist

1. **Use a real image registry** (GHCR/Docker Hub) and deploy by pulling an image instead of building on the server.
2. **Don’t publish Postgres/Redis ports** to the public internet (keep them on the Docker network only).
3. Put Eventara behind a reverse proxy (Nginx/Caddy) for TLS and (later) auth.
4. Replace the default credentials in `.env` and store secrets in your VM’s secret manager.
- [ ] Change default database password in `.env`
- [ ] Put behind a reverse proxy (Nginx/Caddy) for TLS
- [ ] Don't expose Postgres/Redis ports to the internet (prod compose already doesn't)
- [ ] Use a managed database if scaling beyond a single node
35 changes: 23 additions & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
# ===== Stage 1: Build the app =====
FROM maven:3.9.9-eclipse-temurin-21 AS build
# ===== Stage 1: Build the dashboard =====
FROM node:20-alpine AS dashboard-build
WORKDIR /app/eventara-dashboard
COPY eventara-dashboard/package*.json ./
RUN npm ci --production=false
COPY eventara-dashboard/ .
# Vite builds to ../src/main/resources/static (relative to eventara-dashboard)
RUN npm run build

# ===== Stage 2: Build the Spring Boot app =====
FROM maven:3.9.9-eclipse-temurin-21 AS backend-build
WORKDIR /app
COPY pom.xml .
COPY src ./src
# Copy dashboard build output into Spring Boot static resources
COPY --from=dashboard-build /app/src/main/resources/static ./src/main/resources/static/
Comment on lines +1 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Dockerfile comment says Vite builds to ../src/main/resources/static relative to eventara-dashboard, but the actual copy step pulls from /app/src/main/resources/static (i.e., outside /app/eventara-dashboard). That only works if the Vite config truly outputs to /app/src/main/resources/static inside the container.

Given the build stage WORKDIR is /app/eventara-dashboard, a common output would be /app/eventara-dashboard/dist. If the Vite output path ever changes, the Docker build will silently produce an image without the dashboard.

This is fragile because it relies on a non-standard output directory and a hard-coded cross-directory path.

Suggestion

Make the dashboard artifact path explicit and robust:

  • Prefer building to the default dist/ and then copying into backend resources:
    • COPY --from=dashboard-build /app/eventara-dashboard/dist ./src/main/resources/static/

If you intentionally build directly into ../src/main/resources/static, add a quick sanity check to fail fast:

  • RUN test -f /app/src/main/resources/static/index.html

Reply with "@CharlieHelps yes please" if you'd like me to add a commit that switches to copying from dist/ (or adds the fail-fast check).

RUN mvn clean package -DskipTests

# ===== Stage 2: Run the app =====
FROM eclipse-temurin:21-jdk
# ===== Stage 3: Run the app =====
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar

# Run as non-root
RUN groupadd -r eventara && useradd -r -g eventara eventara

COPY --from=backend-build /app/target/*.jar app.jar
RUN chown eventara:eventara app.jar

USER eventara
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Comment on lines 30 to 31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JAVA_OPTS is set in the container environment, but the Dockerfile ENTRYPOINT never consumes it (it always runs java -jar app.jar). Unless your base image provides a wrapper that reads JAVA_OPTS (Temurin images typically do not), this variable will be ignored and the memory tuning guidance in .env.example/docs won’t work.

This is a functional issue: users will think memory limits are applied when they aren’t.

Suggestion

Update the entrypoint to actually apply JAVA_OPTS. Common patterns:

  • Shell form:
    • ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]

Or keep exec form by using CMD:

  • ENTRYPOINT ["sh", "-c"]
  • CMD ["exec java $JAVA_OPTS -jar app.jar"]

Reply with "@CharlieHelps yes please" if you'd like me to add a commit implementing this change safely (including quoting/exec).


# Simplified Dockerfile using local JAR
# FROM eclipse-temurin:21-jdk
# WORKDIR /app
# COPY target/*.jar app.jar
# EXPOSE 8080
# ENTRYPOINT ["java", "-jar", "app.jar"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Core stack in one Docker Compose file. Full stack in 30 seconds.
| Layer | Technology |
|---|---|
| Runtime | Java 21, Spring Boot 3.5.7 |
| Messaging | Apache Kafka 3.7 (KRaft mode; no Zookeeper) |
| Messaging | Apache Kafka (KRaft mode; no Zookeeper) |
| Time-series | TimescaleDB (PostgreSQL 14) |
| Cache / Metrics | Redis 7 |
| Rule Engine | Java handler pattern (Drools for DRL validation) |
Expand Down
63 changes: 42 additions & 21 deletions docker-compose.prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ services:
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
deploy:
resources:
limits:
memory: 1g

redis:
image: redis:7-alpine
Expand All @@ -29,42 +34,51 @@ services:
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
deploy:
resources:
limits:
memory: 512m

kafka:
image: bitnami/kafka:3.7
image: apache/kafka:latest
restart: unless-stopped
environment:
ALLOW_PLAINTEXT_LISTENER: "yes"
KAFKA_KRAFT_CLUSTER_ID: ${KAFKA_KRAFT_CLUSTER_ID:-NvDmnaWzQgiH8qbnraqxcg}
CLUSTER_ID: ${CLUSTER_ID:-NvDmnaWzQgiH8qbnraqxcg}

KAFKA_CFG_NODE_ID: 1
KAFKA_CFG_PROCESS_ROLES: controller,broker
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka:9094
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: controller,broker
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9094
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER

# Internal-only listener for production (no host port published)
KAFKA_CFG_LISTENERS: INTERNAL://:9092,CONTROLLER://:9094
KAFKA_CFG_ADVERTISED_LISTENERS: INTERNAL://kafka:9092
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_CFG_INTER_BROKER_LISTENER_NAME: INTERNAL
KAFKA_LISTENERS: INTERNAL://:9092,CONTROLLER://:9094
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL

KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true"
KAFKA_CFG_NUM_PARTITIONS: 3
KAFKA_CFG_DEFAULT_REPLICATION_FACTOR: 1
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
KAFKA_NUM_PARTITIONS: 3
KAFKA_DEFAULT_REPLICATION_FACTOR: 1

KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_CFG_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
volumes:
- kafka_data:/bitnami/kafka
- kafka_data:/opt/kafka/kafka-logs
Comment on lines 43 to +69

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using apache/kafka:latest is a reproducibility and stability risk. A new upstream release can change defaults, KRaft metadata behavior, or paths, and your deployment could break on the next docker compose up.

Given this PR’s goal is to “production-harden” deployment, the image should be pinned to a specific Kafka version (or at least a major/minor tag) and ideally to a digest.

Suggestion

Pin the Kafka image to a known-good version and (optionally) a digest, e.g.:

  • image: apache/kafka:3.7.2 (or whatever you validated)
  • or image: apache/kafka@sha256:<digest>

Do the same in docker-compose.yaml.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit pinning the image tag in both compose files.

networks:
- eventara-network
healthcheck:
test: ["CMD", "kafka-topics.sh", "--bootstrap-server", "kafka:9092", "--list"]
test: ["CMD", "/opt/kafka/bin/kafka-topics.sh", "--bootstrap-server", "kafka:9092", "--list"]
interval: 10s
timeout: 10s
retries: 5
start_period: 30s
deploy:
resources:
limits:
memory: 1g

eventara:
image: eventara:latest
Expand All @@ -80,7 +94,7 @@ services:
redis:
condition: service_healthy
ports:
- "8080:8080"
- "${EVENTARA_PORT:-8080}:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:-eventara}
SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:-postgres}
Expand All @@ -92,8 +106,15 @@ services:
SPRING_DATA_REDIS_PORT: 6379

EVENTARA_METRICS_DISTRIBUTED_ENABLED: "${EVENTARA_METRICS_DISTRIBUTED_ENABLED:-true}"

# JVM memory — adjust based on your VPS RAM
JAVA_OPTS: "${JAVA_OPTS:--Xms256m -Xmx512m}"
networks:
- eventara-network
deploy:
resources:
limits:
memory: 1g

volumes:
postgres_data:
Expand Down
Loading