MiniTwit implementation for the ITU DevOps course. The repository contains a Spring Boot backend, a Next.js frontend, PostgreSQL persistence, Docker Compose setups, Docker Swarm stack files, Terraform infrastructure files, GitHub Actions workflows, and monitoring/logging configuration.
- Public timeline and user timeline UI.
- User registration and login UI.
- Posting messages from the timeline UI.
- Follow and unfollow actions in the UI.
- Simulator-style backend endpoints for
/register,/msgs,/fllws, and/latest. - Prometheus metrics endpoint exposed by Spring Boot Actuator.
- Grafana dashboards for system, business, and backend-log views.
| Area | Files show |
|---|---|
| Backend | Java 21, Spring Boot 4.0.2, Spring Web, Spring Data JPA, Maven |
| Frontend | Next.js 16.1.6, React 19.2.3, Tailwind CSS, npm |
| Database | PostgreSQL 16 in Docker Compose and Swarm files |
| Monitoring | Prometheus, Grafana, node-exporter, Micrometer |
| Logging | Grafana Alloy, Loki, Grafana, SLF4J, Log4j |
| Deployment | Docker, Docker Compose, Docker Swarm, Traefik |
| Infrastructure | Terraform with DigitalOcean provider |
| CI/CD | GitHub Actions, Docker Hub image publishing, Cosign signing |
Architecture Diagram (click to expand)
graph TD
Client["Browser / Client"]
Simulator["Simulator / API Client"]
subgraph Edge["Deployment Edge"]
Traefik["Traefik Reverse Proxy"]
end
subgraph Presentation["Presentation Layer"]
Next["Next.js Frontend"]
end
subgraph API["API Layer"]
Spring["Spring Boot API"]
WebReq["Frontend-facing API Requests<br/>timeline, user lookup, registration, follow/message, stats"]
SimPublic["Unprotected Simulator/API Requests<br/>POST register, latest"]
SimAuth["Simulator Basic Auth Filter<br/>for msgs/fllws"]
SimReq["Protected Simulator Requests<br/>msgs, fllws"]
end
subgraph Business["Business Layer"]
DatabaseService["DatabaseService"]
Store["Store Implementation<br/>JPA-backed"]
end
subgraph Data["Data Layer"]
Repositories["Spring Data JPA Repositories"]
DB[("PostgreSQL")]
end
subgraph Monitoring["Monitoring / Logging"]
NodeExporter["Node Exporter"]
Prometheus["Prometheus"]
Grafana["Grafana"]
Logs["Alloy + Loki"]
end
subgraph Deployment["CI/CD & Deployment"]
Actions["GitHub Actions"]
Registry["Docker Hub"]
Swarm["DigitalOcean Host(s)<br/>Docker Swarm stacks"]
end
subgraph External["External Services"]
Gravatar["Gravatar"]
LetsEncrypt["Let's Encrypt"]
end
Client --> Traefik
Simulator --> Traefik
Traefik --> Next
Traefik --> Spring
Next -->|API calls| Spring
Next -->|avatar images| Gravatar
Traefik -->|TLS certificates| LetsEncrypt
Spring --> WebReq
Spring --> SimPublic
Spring --> SimAuth
SimAuth --> SimReq
WebReq --> DatabaseService
SimPublic --> Store
SimReq --> Store
DatabaseService --> Repositories
Store --> Repositories
Repositories --> DB
Prometheus -->|scrapes app metrics| Spring
Prometheus -->|scrapes host metrics| NodeExporter
Grafana -->|queries metrics| Prometheus
Grafana -->|queries logs| Logs
Logs -->|Alloy collects container logs| Swarm
Actions -->|builds and pushes images| Registry
Registry --> Swarm
Actions -->|deploy stacks| Swarm
Swarm --> Traefik
Swarm --> Next
Swarm --> Spring
Swarm --> DB
Swarm --> Prometheus
Swarm --> Grafana
Swarm --> Logs
Swarm --> NodeExporter
Our setup uses two Ubuntu-based DigitalOcean droplets. The primary server hosts the PostgreSQL database, Traefik reverse proxy/load balancer, and acts as the Docker Swarm manager. The second server acts as a Docker Swarm worker and hosts the frontend and backend application services. Docker Swarm coordinates deployment and service management across both servers.
The application is a Twitter-inspired system with a React/Next.js frontend and a Java 21 Spring Boot backend API. The frontend serves the client-facing interface, while the backend handles requests and communicates with the database. PostgreSQL runs as a Docker container with persistent storage and is only accessible internally on port 5432.
Monitoring and logging are handled using Prometheus, Loki, and Grafana, which are described in later sections.
| Path | Purpose |
|---|---|
devops_backend/ |
Spring Boot backend source, Maven config, resources, and Dockerfile. |
devops_frontend/ |
Next.js frontend source, npm config, styles, assets, and Dockerfile. |
docker-compose.yml |
Local stack with app, database, monitoring, and logging services. |
docker-compose.ci.yml |
Backend + PostgreSQL stack used by API tests. |
docker-compose.prod.yml |
Image-based Compose stack. |
swarm/ |
Swarm stack files for app, Traefik proxy, monitoring, and visualizer. |
monitoring/ |
Prometheus config and Grafana provisioning files. |
alloy/config.alloy |
Docker log discovery and Loki forwarding config. |
terraform/ |
DigitalOcean droplet definitions and ignored Terraform state files. |
.github/workflows/ |
Backend CI, frontend CI, CD, and report PDF workflows. |
test.py |
Backend API tests. |
test_ui.py |
Playwright UI tests with mocked API responses. |
LOGGING.md |
Short logging explanation. |
report/report.md |
Course report Markdown file with section headings. |
The backend application name is itu_minitwit, and
devops_backend/src/main/resources/application.properties sets server.port to
5001.
Backend routes visible in controller files:
- General web/API routes:
GET /,/user,/register,/spec_user,/is_followed,/follow,/unfollow,/add_message,/stats, and/ping. - Simulator/API-test routes:
POST /register,GET /latest,/msgs, and/fllws.
SimulatorAuthFilter.java protects paths starting with /msgs and /fllws
with a fixed Basic Auth header. test.py uses simulator:super_safe!.
The frontend app files are in devops_frontend/app/:
/redirects to/timeline./timelinerenders the timeline UI./loginrenders the login UI./registerrenders the registration UI.
The frontend builds API URLs from NEXT_PUBLIC_API_HOST and
NEXT_PUBLIC_API_PORT.
Backend defaults in application.properties:
| Setting | Value |
|---|---|
server.port |
5001 |
MINITWIT_DB_URL fallback |
jdbc:postgresql://167.172.111.97:5432/minitwit |
MINITWIT_DB_USER fallback |
minitwit_user |
MINITWIT_DB_PASSWORD fallback |
minitwit_password |
| exposed actuator endpoints | health,info,prometheus |
Frontend local env file:
NEXT_PUBLIC_API_HOST=http://localhost
NEXT_PUBLIC_API_PORT=5001Ports in docker-compose.yml:
| Service | Host port |
|---|---|
| frontend | 3000 |
| backend | 5001 |
| PostgreSQL | 5432 |
| Prometheus | 9090 |
| Grafana | 3001 |
| Loki | 3100 |
| Alloy | 12345 |
docker-compose.yml uses ${GRAFANA_PASSWORD} for Grafana's admin password.
Run the local Docker Compose stack:
GRAFANA_PASSWORD=<local-password> docker compose up --buildOpen the frontend:
http://localhost:3000
Useful backend checks:
curl http://localhost:5001/ping
curl http://localhost:5001/actuator/health
curl http://localhost:5001/actuator/prometheusStop the local stack:
docker compose down -vRun the backend directly:
cd devops_backend
mvn spring-boot:runRun frontend commands:
cd devops_frontend
npm install
npm run dev
npm run build
npm run start
npm run lintThe frontend Dockerfile uses Node 20-alpine, runs npm ci, builds with
npm run build, exposes port 3000, and starts Next on 0.0.0.0:3000.
Backend API tests:
docker compose -f docker-compose.ci.yml up -d --build backend
pytest -v -x test.py
docker compose -f docker-compose.ci.yml down -vFrontend UI tests are present in test_ui.py and use Playwright route mocks.
The frontend CI workflow installs Playwright dependencies, but its test step is
an echo TODO rather than pytest -v -x test_ui.py because of some problems we had.
Backend Maven tests:
cd devops_backend
mvn testFrontend lint:
cd devops_frontend
npm run lintci_backend.yml runs Maven tests, starts docker-compose.ci.yml, runs
test.py, runs Hadolint on the backend Dockerfile, and builds/pushes/signs the
backend Docker image outside pull requests.
ci_frontend.yml starts the full Compose stack, waits for frontend/backend
URLs with curl, installs Playwright dependencies, runs ESLint, and
builds/pushes/signs the frontend Docker image outside pull requests.
cd.yml runs on pushes to main and manual dispatch. It SSHes to
167.172.111.97 as root, runs git pull in /root/devops, and deploys the
Swarm stacks:
docker stack deploy --compose-file swarm/proxy-stack.yml proxy --resolve-image always
docker stack deploy --compose-file swarm/app-stack.yml app --resolve-image always
docker stack deploy --compose-file swarm/docker-swarm-ui.yml swarm --resolve-image always
docker stack deploy --compose-file swarm/monitoring-stack.yml monitoring --resolve-image alwaysbuild-report.yml builds report/report.md into a PDF artifact.
swarm/proxy-stack.yml configures Traefik v3.6 with ports 80, 443, and
5001, HTTP-to-HTTPS redirection, access logs, Let's Encrypt using
${ACME_EMAIL}, and the Docker Swarm provider.
swarm/app-stack.yml configures:
- Backend image
andersfrimann/devops_backend:mainwith3replicas. - Frontend image
andersfrimann/devops_frontend:main. - PostgreSQL
16, constrained tonode.hostname == MiniTwit-Database. - External network
minitwit-publicand overlay networkbackend-db. - Traefik routes for
api.rollbackandrelax.dkandrollbackandrelax.dk.
swarm/monitoring-stack.yml configures node-exporter, Prometheus, Loki, Alloy,
and Grafana. Alloy is global. Grafana has a Traefik route for
grafana.rollbackandrelax.dk.
swarm/docker-swarm-ui.yml configures dockersamples/visualizer:latest with a
Traefik route for swarm.rollbackandrelax.dk.
Prometheus scrapes:
backend:5001at/actuator/prometheusnode-exporter:9100
Grafana provisioning defines:
- Prometheus datasource at
http://prometheus:9090 - Loki datasource at
http://loki:3100 - Dashboard provider path
/etc/grafana/provisioning/dashboards
Tracked dashboards:
systemDashboard.jsonbusinessDashboard.jsonminitwit-logs.json
See LOGGING.md for the logging summary.
terraform/provider.tf uses the DigitalOcean provider and defines:
| Resource | Name | Region | Size | Image |
|---|---|---|---|---|
digitalocean_droplet.node |
MiniTwit-Database |
fra1 |
s-2vcpu-4gb |
ubuntu-24-04-x64 |
digitalocean_droplet.node2 |
MiniTwit-Worker |
fra1 |
s-1vcpu-2gb |
ubuntu-24-04-x64 |
Both resources set backups = true and monitoring = true.
The workflow files show this path for changes intended to reach the configured Swarm deployment:
- Change code, configuration, or documentation.
- Run the relevant local commands from Tests.
- Open a pull request to
main - Let the configured GitHub Actions workflows run.
- Merge or push to
mainto trigger the configured CD workflow.
RollbackAndRelax — MSc DevOps, IT University of Copenhagen, Spring 2026
- Alperen Aydin
- Maria Møller
- Juliane Falsig Hvid
- Anders Frimann