Skip to content

chaprana-sujal/De-ploi-er

Repository files navigation

De-Ploi-er —> A Mini Platform-as-a-Service

A lightweight PaaS (Platform-as-a-Service) built with Spring Boot that lets you deploy any Dockerized application from a GitHub repository with a single API call. It clones, builds, and runs your app behind a Traefik reverse proxy — each project gets its own subdomain at <project-name>.localhost.


Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                        YOUR MACHINE                             │
│                                                                 │
│  ┌──────────────────────────┐     ┌──────────────────────────┐  │
│  │  Spring Boot PaaS App    │     │   PostgreSQL (port 5432) │  │
│  │  (port 8080)             │────▶│   Stores projects &      │  │
│  │                          │     │   deployment records     │  │
│  │  REST API:               │     └──────────────────────────┘  │
│  │   POST /api/projects     │                                   │
│  │   POST /api/projects/    │     ┌──────────────────────────┐  │
│  │        {id}/deploy       │────▶│   Docker Daemon          │  │
│  │   GET  /api/deployments  │     │   Builds images &        │  │
│  └──────────────────────────┘     │   runs containers        │  │
│                                   └──────────┬───────────────┘  │
│                                              │                  │
│  ┌───────────────────────────────────────────┼───────────────┐  │
│  │              Docker Network: Paas-net     │               │  │
│  │                                           │               │  │
│  │  ┌────────────┐  ┌────────────┐  ┌────────┴───────┐      │  │
│  │  │ Traefik    │  │ App 1      │  │ App 2          │      │  │
│  │  │ port 80    │  │ app1.local │  │ app2.localhost  │      │  │
│  │  │ (reverse   │──│ host       │  │                 │      │  │
│  │  │  proxy)    │  └────────────┘  └─────────────────┘      │  │
│  │  └────────────┘                                           │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Module Breakdown

1. Models (com.sujal.PaasDemo.Models)

Class Purpose
Project Represents a registered application. Stores the name, repositoryUrl, subdomain, containerId (active Docker container), and optional env vars (as a @ElementCollection map).
Deployment Represents a single deployment attempt. Tracks the status (enum), logs (TEXT column for real-time build output), commitHash, containerId, and timestamps. Has a @ManyToOne relationship to Project.
DeploymentStatus Enum defining the deployment lifecycle: PENDINGCLONINGBUILDINGDEPLOYINGRUNNING (or FAILED).

How they relate:

Project 1 ──────< Deployment 1 (RUNNING)
                  Deployment 2 (FAILED)
                  Deployment 3 (PENDING)

One project can have many deployments. Each deployment goes through the status pipeline independently.


2. Controller (com.sujal.PaasDemo.controller)

DeploymentController — The REST API layer. No business logic here, just HTTP ↔ Service wiring.

Endpoint Method What It Does
/api/projects POST Register a new project (name + GitHub URL)
/api/projects GET List all registered projects
/api/projects/{id} GET Get a specific project
/api/projects/{id}/deploy POST Trigger a deployment — creates a Deployment record in PENDING, then kicks off the async pipeline
/api/deployments/{id} GET Poll deployment status & logs
/api/deployments GET List all deployments

Key behavior: The deploy endpoint returns 202 Accepted immediately with the deployment record. The actual work happens asynchronously — the client polls GET /api/deployments/{id} to watch progress.


3. Services — Orchestration (com.sujal.PaasDemo.Services.Orchestration)

Orchestrationservice — The brain of the deployment pipeline. Annotated with @Async so it runs in a background thread.

Pipeline steps:

deployProject(deploymentId)
    │
    ├── 1. VALIDATE    → Check project has a repository URL
    │
    ├── 2. CLONING     → Call GitService.cloneRepository()
    │                    Clones repo to a temp directory
    │
    ├── 3. BUILDING    → Call DockerService.buildImage()
    │                    Builds Docker image from the repo's Dockerfile
    │
    ├── 4. CLEANUP     → Stop old container (if project was previously deployed)
    │
    ├── 5. DEPLOYING   → Call DockerService.createAndStartContainer()
    │                    Creates container with Traefik labels on Paas-net
    │
    └── 6. RUNNING     → Update project's containerId, clean up temp files

Error handling: Any exception at any step catches into FAILED status with the full stack trace saved into the deployment's logs field.

Real-time logging: Each step calls appendLog() which saves a timestamped message to the database immediately, so you can poll GET /api/deployments/{id} to see progress.


4. Services — Git (com.sujal.PaasDemo.Services.git)

Class Purpose
GitService Interface — cloneRepository(String repoUrl)
GitServiceImpl Implementation using JGit library

Smart URL handling:

  • Browser URLs like https://github.com/user/repo/tree/main are automatically normalized to https://github.com/user/repo.git
  • Branch is extracted from the URL (e.g., /tree/develop → clones develop branch)
  • 60-second clone timeout to prevent hanging on unreachable repos

Clone flow:

Input URL → extractBranch() → normalizeGitUrl() → JGit clone to temp dir → return File

5. Services — Docker (com.sujal.PaasDemo.Services.docker)

DockerServiceImpl — Wraps the docker-java client library.

Method What It Does
buildImage(dir, tag) Pings Docker daemon first (fail-fast check), verifies Dockerfile exists, builds with streaming log output, 5-minute timeout
createAndStartContainer(imageId, appName) Creates container named Paas-<appName> on Paas-net network with Traefik routing labels
stopContainer(containerId) Stops and removes a container

Traefik labels applied to each container:

traefik.enable = true
traefik.http.routers.<appName>.rule = Host(`<appName>.localhost`)
traefik.http.services.<appName>.loadbalancer.server.port = 8080

This means the deployed app must listen on port 8080 inside its container.


6. Config (com.sujal.PaasDemo.config)

Class Purpose
DockerConfig Creates the DockerClient bean using default Docker host (connects to Docker Desktop)
SecurityConfig Disables CSRF and permits all requests (no auth — development mode)
StartupCleanup Runs on app startup — finds any deployments stuck in PENDING/CLONING/BUILDING/DEPLOYING and marks them FAILED. Prevents stale deployments from previous crashes.

7. Repositories (com.sujal.PaasDemo.repo)

Interface Purpose
projectrepo JPA repository for Project — standard CRUD
deploymentrepo JPA repository for Deployment — standard CRUD + findByStatusIn(List<DeploymentStatus>) for startup cleanup

8. Infrastructure (compose.yaml)

Runs Traefik as a Docker container:

  • Listens on port 80 for HTTP traffic
  • Dashboard available at port 8081
  • Watches Docker events to auto-discover containers with Traefik labels
  • Must be on the same Paas-net network as deployed containers

Prerequisites

  1. Java 25 (as specified in build.gradle)
  2. Docker Desktop — must be running
  3. PostgreSQL — running on localhost:5432 with database postgres
  4. Docker network — create once:
    docker network create Paas-net

Quick Start

1. Start Traefik (reverse proxy)

cd PaasDemo
docker compose up -d

2. Start the PaaS application

./gradlew.bat bootRun

The API will be available at http://localhost:8080

3. Register a project

curl -X POST http://localhost:8080/api/projects \
  -H "Content-Type: application/json" \
  -d '{"name": "my-app", "repositoryUrl": "https://github.com/username/repo"}'

Important: The repository must contain a Dockerfile at its root, and the app inside must listen on port 8080.

4. Deploy the project

curl -X POST http://localhost:8080/api/projects/1/deploy

5. Monitor deployment

curl http://localhost:8080/api/deployments/1

Watch the status field progress: PENDINGCLONINGBUILDINGDEPLOYINGRUNNING

The logs field shows real-time output from each step.

6. Access your deployed app

Once status is RUNNING, open:

http://my-app.localhost

(Replace my-app with whatever project name you used in step 3)


API Reference

Projects

Method Endpoint Body Response
POST /api/projects {"name": "...", "repositoryUrl": "..."} 201 + Project JSON
GET /api/projects 200 + List of Projects
GET /api/projects/{id} 200 + Project JSON or 404

Deployments

Method Endpoint Body Response
POST /api/projects/{id}/deploy 202 + Deployment JSON (PENDING)
GET /api/deployments/{id} 200 + Deployment JSON with status & logs
GET /api/deployments 200 + List of all Deployments

Deployment Lifecycle

PENDING ──► CLONING ──► BUILDING ──► DEPLOYING ──► RUNNING
   │            │            │            │
   └────────────┴────────────┴────────────┴──► FAILED (on any error)

Each status transition is persisted to the database immediately, so polling GET /api/deployments/{id} gives real-time progress.


Project Structure

PaasDemo/
├── compose.yaml                          # Traefik reverse proxy
├── build.gradle                          # Dependencies & config
├── Dockerfile                            # (For containerizing PaaS itself)
└── src/main/java/com/sujal/PaasDemo/
    ├── PaasDemoApplication.java          # Entry point (@EnableAsync)
    ├── Models/
    │   ├── Project.java                  # Project entity
    │   ├── Deployment.java               # Deployment entity
    │   └── DeploymentStatus.java         # Status enum
    ├── controller/
    │   └── DeploymentController.java     # REST API
    ├── Services/
    │   ├── Orchestration/
    │   │   └── Orchestrationservice.java # Async deployment pipeline
    │   ├── git/
    │   │   ├── GitService.java           # Interface
    │   │   └── GitServiceImpl.java       # JGit clone implementation
    │   └── docker/
    │       └── DockerServiceImpl.java    # Docker build & run
    ├── config/
    │   ├── DockerConfig.java             # DockerClient bean
    │   ├── SecurityConfig.java           # Permit all (dev mode)
    │   └── StartupCleanup.java           # Clean stale deployments
    └── repo/
        ├── projectrepo.java              # Project JPA repo
        └── deploymentrepo.java           # Deployment JPA repo

Troubleshooting

Problem Cause Solution
Deployment stuck at BUILDING Docker Desktop not running, or build hangs The app now pings Docker before building and has a 5-min timeout. Restart the app to auto-mark stale deployments as FAILED.
502 Bad Gateway at *.localhost App inside container not listening on port 8080 Ensure your Dockerfile configures the app to listen on port 8080
404 at *.localhost Traefik can't find a matching route Check container is on Paas-net network and has correct Traefik labels
Connection refused at *.localhost Traefik not running Run docker compose up -d in the PaasDemo directory
Port 8080 conflict on docker compose up Spring Boot already uses 8080 Traefik dashboard is mapped to 8081 to avoid this

About

implemetaion of a Paas service that automates the deplyment work over a local docker network

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors