An Open Source, Microservices-based Vercel Clone built with Spring Boot.
JStratusD is a robust Platform-as-a-Service (PaaS) engine designed to mimic the core deployment capabilities of Vercel. It enables developers to upload Git repository URLs, automatically triggers build pipelines via microservices, and serves static assets through a custom edge-like request handler.
The JStratusD system mimics the core infrastructure of Vercel using a distributed, event-driven microservices architecture designed for high scalability and fault tolerance. A central API Gateway (Spring Cloud Gateway) routes user traffic to the appropriate services, while an Upload Service orchestrates the ingestion of code via Git cloning and handles GitHub Webhook events for automatic deployments. To prevent blocking the user-facing API during resource-intensive operations, build tasks are pushed to a Redis queue and processed asynchronously by stateless Deploy Service workers running in isolated Alpine Linux containers. These workers execute npm install and build commands, stream logs in real-time to a PostgreSQL database for user visibility, and upload the final static artifacts to Cloudflare R2 (S3-compatible storage). Finally, a Cloudflare Worker at the edge dynamically intercepts incoming requests, fetches the correct static assets from storage based on the project ID, and serves the site globally with low latency, effectively decoupling the build engine from the serving layer.
graph TD
User(User / Browser)
subgraph Infrastructure
Gateway(API Gateway - Port 8080)
Eureka(Eureka Server - Port 8761)
Postgres[(PostgreSQL DB)]
Redis[(Redis - Queue)]
end
subgraph Ingestion_Layer
UserService(User Service - Port 8081)
UploadService(Upload Service - Port 8082)
end
subgraph Processing_Layer
DeployService(Deploy Service - Port 9091)
end
subgraph Serving_Layer
Cloudflare(Cloudflare Worker / Edge)
Storage(Cloudflare R2 - S3 Storage)
end
%% Auth Flow
User -->|POST /auth/login| Gateway
Gateway -->|Route| UserService
UserService -->|Read/Write| Postgres
%% Deployment Flow
User -->|POST /deployments| Gateway
Gateway -->|Route| UploadService
UploadService -->|JGit Clone| GitService[GitHub]
UploadService -->|Save Metadata| Postgres
UploadService -->|Push Job ID| Redis
%% Build Flow
Redis -->|Poll Job| DeployService
DeployService -->|Fetch Status| Postgres
DeployService -->|npm install & build| DeployService
DeployService -->|Stream Logs| Postgres
DeployService -->|Upload Artifacts| Storage
%% Serving Flow
User -->|GET project.workers.dev| Cloudflare
Cloudflare -->|Fetch HTML/JS| Storage
Storage -->|Return Content| Cloudflare
Cloudflare -->|Serve Site| User
- Core Framework: Java 17, Spring Boot 3.2.x
- Build Tool: Maven
- Message Broker: Redis (Pub/Sub & Queue)
- Storage: AWS S3 / MinIO
- Version Control: JGit
- Containerization: Docker
Vercel-Upload-Service/
βββ Dockerfile <-- Standard Java Dockerfile
βββ pom.xml <-- Dependencies (Web, JPA, Redis, S3, Eureka Client)
βββ src/
β βββ main/
β β βββ resources/
β β β βββ application.yml <-- Config: Port 8082, DB, Redis, S3, Eureka
β β β
β β βββ java/org/godn/verceluploadservice/
β β β
β β βββ VercelUploadServiceApplication.java <-- @EnableAsync, @EnableDiscoveryClient
β β β
β β βββ config/
β β β βββ AppConfig.java <-- General Beans
β β β βββ AsyncConfig.java <-- @EnableAsync Configuration
β β β
β β βββ deployment/ <-- DOMAIN: Shared Logic & Data
β β β βββ Deployment.java (Entity)
β β β βββ DeploymentStatus.java (Enum: QUEUED, BUILDING, READY...)
β β β βββ DeploymentRepository.java (DB Access)
β β β βββ DeploymentService.java (Logic: Limits, Cancel, Delete, Get)
β β β βββ DeploymentController.java (API: POST /deploy, GET /status, DELETE)
β β β βββ DeploymentResponseDto.java(Output DTO)
β β β βββ BuildLog.java (Entity: Logs)
β β β βββ BuildLogRepository.java (DB Access: Logs)
β β β βββ ProjectSecret.java (Entity: Env Vars)
β β β βββ ProjectSecretRepository.java (DB Access: Env Vars)
β β β
β β βββ upload/ <-- FEATURE: Ingestion
β β β βββ UploadService.java (Orchestrator: Git -> S3 -> Redis)
β β β βββ UploadController.java (Can be merged into DeploymentController)
β β β βββ UploadRequestDto.java (Input: repoUrl, secrets)
β β β βββ UploadResponseDto.java (Output: id, status)
β β β βββ SecretsDto.java (Input: Map of secrets)
β β β
β β βββ controller/ <-- FEATURE: Webhooks
β β β βββ WebhookController.java (GitHub Push Events)
β β β
β β βββ queue/ <-- INFRASTRUCTURE: Redis
β β β βββ RedisQueueService.java (Producer: pushToQueue)
β β β
β β βββ storage/ <-- INFRASTRUCTURE: S3/R2
β β β βββ S3UploadService.java (Synchronous Upload Logic)
β β β
β β βββ util/
β β β βββ GenerateId.java (Base62 Generator)
β β β
β β βββ exception/ <-- ERROR HANDLING
β β βββ GlobalExceptionHandler.java
β β βββ BadRequestException.java
β β βββ ResourceNotFoundException.java
β β βββ UnauthorizedException.java
Vercel-Deploy-Service/
βββ Dockerfile <-- Special: Alpine + Node.js 20 Installed
βββ pom.xml <-- Dependencies (JPA, Redis, S3, Eureka Client - Exclude Jersey!)
βββ src/
β βββ main/
β β βββ resources/
β β β βββ application.yml <-- Config: Port 9091, Same DB/Redis/S3 Credentials
β β β
β β βββ java/org/godn/verceldeployservice/
β β β
β β βββ VercelDeployServiceApplication.java <-- @EnableDiscoveryClient
β β β
β β βββ config/
β β β βββ BuildExecutorConfig.java <-- Thread Pool Config (Size = 1)
β β β
β β βββ deployment/ <-- DOMAIN: COPIED FROM UPLOAD SERVICE
β β β βββ Deployment.java (Must match Upload Service exactly)
β β β βββ DeploymentStatus.java (Must match Upload Service exactly)
β β β βββ DeploymentRepository.java (Must match Upload Service exactly)
β β β βββ BuildLog.java (Must match Upload Service exactly)
β β β βββ BuildLogRepository.java (Must match Upload Service exactly)
β β β βββ ProjectSecret.java (Must match Upload Service exactly)
β β β βββ ProjectSecretRepository.java (Must match Upload Service exactly)
β β β
β β βββ queue/ <-- CORE LOGIC
β β β βββ RedisQueueService.java (Consumer: popFromQueue)
β β β βββ RedisListenerService.java (The Brain: Poll -> Lock -> Build -> Update)
β β β
β β βββ build/ <-- FEATURE: Building
β β β βββ BuildService.java (npm install -> npm run build -> Save Logs)
β β β
β β βββ download/ <-- FEATURE: Downloading
β β β βββ DownloadService.java (Orchestrator: Download S3 folder)
β β β
β β βββ service/ <-- FEATURE: Uploading Artifacts
β β β βββ BuildUploadService.java (Orchestrator: Upload 'dist' folder)
β β β
β β βββ storage/ <-- INFRASTRUCTURE: S3/R2
β β βββ S3UploadService.java (Synchronous Upload Logic - Same as Upload Service)
β β βββ S3DownloadService.java (Synchronous Download Logic)
β β βββ S3Properties.java (Configuration Mapping)
- Java 17+
- Maven 3.8+
- Redis
- Docker (optional)
docker run -d --name jstratus-redis -p 6379:6379 redis:alpinegit clone https://github.com/GoDn76/JStratusD.git
cd JStratusDUsing .env.example include all the required values
DB_URL=
DB_USERNAME=
DB_PASSWORD=
BUILD_QUEUE=
R2_REGION=
R2_BUCKET_NAME=
R2_ACCESS_KEY=
R2_SECRET_KEY=
R2_ENDPOINT=
UPSTASH_REDIS_REST_HOST=
UPSTASH_REDIS_REST_TOKEN=
REDIS_PORT=
DB_URL=
DB_USERNAME=
DB_PASSWORD=
BUILD_QUEUE=
R2_REGION=
R2_BUCKET_NAME=
R2_ACCESS_KEY=
R2_SECRET_KEY=
R2_ENDPOINT=
UPSTASH_REDIS_REST_HOST=
UPSTASH_REDIS_REST_TOKEN=
REDIS_PORT=
WORKER_WEBSITE_URL=
(Note - you can use docker compose but for now using docker.)
cd ./Upload-Service
mvn clean package
docker build -t upload-s .
docker run --env-file .\.env --rm -p 8081:8081 upload-scd ./Deploy-Service
mvn clean package
docker build -t deploy-s .
docker run --env-file .\.env --rm deploy-sEndpoint: POST /auth/register
Body:
{
"name": "godn",
"email": "godn@example.com",
"password": "securepassword123"
}Response: 200 OK (JWT Token)
Endpoint: POST /auth/login
Body:
{
"email": "godn@example.com",
"password": "securepassword123"
}Response:
{
"token": "eyJhbGciOiJIUzI1NiJ9...",
"type": "bearer"
}Endpoint: POST /deployments
Body:
{
"repoUrl": "https://github.com/godn/my-react-app.git",
"branch": "main",
"secrets": {
"REACT_APP_API_URL": "https://api.myapp.com"
}
}Response:
{
"success": true,
"message": "Deployment Queued",
"projectId": "0E9L6"
}Endpoint: GET /deployments
Response:
[
{
"id": "0E9L6",
"status": "READY",
"repositoryUrl": "https://github.com/godn/my-react-app.git",
"websiteUrl": "https://vc-r.godn.workers.dev/view/0E9L6",
"createdAt": "2023-10-27T10:00:00"
}
]Endpoint: GET /deployments/{id}
Response: Same DTO as above
Endpoint: POST /deployments/{id}/cancel
Response:
Deployment cancelled successfully.
Endpoint: DELETE /deployments/{id}
Response:
Deployment deleted successfully.
Poll every 2 seconds for real-time updates.
Endpoint: GET /deployments/{id}/logs
Response:
[
{
"id": 101,
"deploymentId": "0E9L6",
"content": "[npm-build] Installing dependencies...",
"timestamp": "2023-10-27T10:00:05"
},
{
"id": 102,
"deploymentId": "0E9L6",
"content": "[npm-build] Build complete.",
"timestamp": "2023-10-27T10:01:20"
}
] Status Meaning
--------------- -----------------------
QUEUED Waiting for worker
BUILDING Installing/building
READY Deployment successful
FAILED Build error
CANCELLED User cancelled
TIMED_OUT Exceeded 20 min limit
- Fork repo
- Create a branch
- Commit changes
- Open a PR
Gaurav Uramliya
MIT License.