Skip to content

YHQZ1/Hatch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hatch

A self-hosted deployment platform built on AWS. Dockerfile in. Live URL out.

Go Next.js AWS Terraform RabbitMQ PostgreSQL License


What is Hatch?

Hatch is a self-hosted deployment platform — a Render / Railway alternative built on AWS — that lets developers go from a GitHub repository to a live, publicly accessible URL without touching any infrastructure.

You bring a Dockerfile. Hatch handles the rest: cloning the repo, building the image, pushing it to ECR, registering an ECS task definition, provisioning a Fargate service, and wiring up an Application Load Balancer. Every step streams back to you in real time via WebSockets.

The core loop:

connect repo → configure → click Deploy
  → live build logs stream to your browser
  → container running on AWS Fargate
  → live URL returned

Hatch supports any language, any framework, any runtime — if it runs in a Docker container, Hatch can deploy it.


Architecture

Hatch is a monorepo of independently deployable microservices communicating through RabbitMQ. The API never calls the builder directly — it publishes a job and moves on.

┌─────────────────────────────────────────────────────────────────┐
│                        Next.js Frontend                         │
│              Dashboard · Deploy UI · Live Log Terminal          │
└──────────────────────────┬──────────────────────────────────────┘
                           │ REST + WebSocket
┌──────────────────────────▼──────────────────────────────────────┐
│                      API Service  (Go · Gin)                    │
│         GitHub OAuth · JWT Auth · WebSocket Hub · CRUD          │
└────────────┬────────────────────────────────────────────────────┘
             │ Publishes BuildJobEvent
             │
     ┌───────▼────────┐         ┌──────────────────────┐
     │   RabbitMQ     │────────▶│   Builder Service    │
     │  Message Queue │         │   (Go)               │
     └───────┬────────┘         │                      │
             │                  │  1. Clone via GitHub │
             │                  │  2. docker build     │
             │                  │  3. Push → AWS ECR   │
             │                  └──────────────────────┘
             │ Publishes DeployJobEvent
             │
             │                  ┌──────────────────────┐
             └─────────────────▶│   Deployer Service   │
                                │   (Go)               │
                                │                      │
                                │  1. Register ECS     │
                                │     Task Definition  │
                                │  2. Launch Fargate   │
                                │  3. Configure ALB    │
                                │  4. Return live URL  │
                                └──────────────────────┘

     Log streaming: all services → Redis pub/sub → WebSocket → Browser
     Data:          PostgreSQL · Redis
     Infra:         Terraform · AWS ECS Fargate · ECR · ALB

Services

Service Language Role
apps/web Next.js 15 Frontend dashboard, deploy UI, real-time log terminal
apps/api Go + Gin GitHub OAuth, REST API, WebSocket hub, RabbitMQ publisher
apps/builder Go Clones repos, builds Docker images, pushes to ECR
apps/deployer Go Provisions ECS services, configures ALB routing

Shared Packages

Package Purpose
packages/db sqlc-generated type-safe database layer + SQL migrations
packages/config Environment variable loader

Deployment Flow

What happens between clicking Deploy and getting a live URL:

1.  User selects repo + branch
2.  User configures: CPU, memory, port, health check path, env vars
3.  POST /api/deployments → creates deployment record (status: queued)
4.  API publishes BuildJobEvent → hatch.build.jobs

--- Builder picks up job ---

5.  Clones repo using GitHub OAuth token
6.  Runs: docker build --platform linux/amd64 -t {image} .
    → each log line published to Redis → WebSocket → browser
7.  Pushes image to AWS ECR
8.  Publishes DeployJobEvent → hatch.deploy.jobs

--- Deployer picks up job ---

9.  Registers ECS Task Definition
10. Creates ALB target group + listener rule (path-based routing)
11. Creates ECS Fargate service
12. Polls until RunningCount >= 1
13. Updates deployment record: status=live, url={alb-dns}/{subdomain}

--- Frontend ---

14. WebSocket receives final log line with live URL
15. URL displayed in terminal

Tech Stack

Layer Technology Why
Frontend Next.js 15 (App Router) Server components, seamless API routes
API Go + Gin Goroutines handle concurrent WebSocket connections trivially
Builder Go Docker SDK + AWS SDK are first-class Go libraries
Deployer Go aws-sdk-go-v2 has excellent ECS/ALB support
Queue RabbitMQ Durable job delivery, dead letter queues for failed builds
Pub-Sub Redis Log streaming bridge between services and WebSocket hub
Database PostgreSQL 16 Relational data, migrations via golang-migrate
Container Runtime AWS ECS Fargate Serverless containers, no EC2 to manage
Container Registry AWS ECR Private Docker image storage
Load Balancer AWS ALB Routes traffic to ECS services via path-based rules
IaC Terraform Entire AWS stack provisioned with terraform apply

Repository Structure

hatch/
├── apps/
│   ├── web/                     # Next.js 15 frontend
│   │   └── src/app/
│   │       ├── (pages)/         # Dashboard, projects, deploy UI
│   │       ├── components/      # Navbar, shared components
│   │       └── page.tsx         # Landing page
│   │
│   ├── api/                     # Go · Gin — public API gateway
│   │   ├── cmd/server/
│   │   └── internal/
│   │       ├── auth/            # GitHub OAuth, JWT middleware
│   │       ├── handlers/        # projects, deployments, github endpoints
│   │       ├── ws/              # WebSocket hub (Redis sub → browser)
│   │       ├── queue/           # RabbitMQ publisher
│   │       └── db/              # Postgres connection
│   │
│   ├── builder/                 # Go — clone → build → ECR push
│   │   ├── cmd/worker/
│   │   └── internal/
│   │       ├── git/             # Repo clone via OAuth token
│   │       ├── docker/          # docker build + ECR push
│   │       ├── logs/            # Redis PUBLISH
│   │       └── queue/           # RabbitMQ consumer + publisher
│   │
│   └── deployer/                # Go — ECS + ALB
│       ├── cmd/worker/
│       └── internal/
│           ├── ecs/             # Task definition, service lifecycle, health polling
│           ├── logs/            # Redis PUBLISH
│           └── queue/           # RabbitMQ consumer
│
├── packages/
│   ├── db/
│   │   ├── migrations/          # SQL migration files
│   │   ├── queries/             # sqlc input SQL
│   │   └── gen/                 # sqlc-generated Go code
│   └── config/                  # Shared env var loader
│
├── infra/
│   ├── modules/
│   │   ├── networking/          # VPC, subnets, security groups
│   │   ├── ecs/                 # ECS cluster, IAM roles
│   │   └── alb/                 # Load balancer, listener, target groups
│   └── envs/dev/                # Dev environment Terraform entry point
│
├── docker-compose.yml           # Local dev infrastructure
├── Makefile
└── go.work                      # Go workspace

Getting Started

Prerequisites

  • Go 1.23+
  • Node.js 20+
  • Docker + Docker Compose
  • AWS CLI configured with IAM credentials
  • Terraform 1.5+

1. Clone and install

git clone https://github.com/YHQZ1/Hatch.git
cd Hatch

cd apps/web && npm install && cd ../..
go work sync

2. Configure environment

cp apps/api/.env.example apps/api/.env
cp apps/builder/.env.example apps/builder/.env
cp apps/deployer/.env.example apps/deployer/.env
cp apps/web/.env.local.example apps/web/.env.local

apps/api/.env

PORT=8080
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_REDIRECT_URI=http://localhost:8080/auth/callback
JWT_SECRET=
DATABASE_URL=postgres://hatch:hatch@localhost:5432/hatch?sslmode=disable
REDIS_URL=redis://localhost:6379
RABBITMQ_URL=amqp://guest:guest@localhost:5672/

apps/builder/.env

RABBITMQ_URL=amqp://guest:guest@localhost:5672/
REDIS_URL=redis://localhost:6379
AWS_REGION=ap-south-1
ECR_REGISTRY=<account-id>.dkr.ecr.ap-south-1.amazonaws.com
ECR_REPOSITORY=hatch-builds

apps/deployer/.env

RABBITMQ_URL=amqp://guest:guest@localhost:5672/
REDIS_URL=redis://localhost:6379
AWS_REGION=ap-south-1
ECS_CLUSTER_NAME=hatch-cluster
ALB_LISTENER_ARN=
VPC_ID=
SUBNET_A=
SUBNET_B=
ECS_SG_ID=
TASK_EXECUTION_ROLE_ARN=
ECR_REGISTRY=<account-id>.dkr.ecr.ap-south-1.amazonaws.com
DATABASE_URL=postgres://hatch:hatch@localhost:5432/hatch?sslmode=disable

3. Start local infrastructure

docker compose up -d postgres redis rabbitmq

4. Run migrations

migrate -path packages/db/migrations \
  -database "postgres://hatch:hatch@localhost:5432/hatch?sslmode=disable" up

5. Run services

# four separate terminals
cd apps/api      && go run cmd/server/main.go
cd apps/builder  && go run cmd/worker/main.go
cd apps/deployer && go run cmd/worker/main.go
cd apps/web      && npm run dev

Open http://localhost:3000.

6. Provision AWS infrastructure

# create S3 bucket for Terraform state
aws s3 mb s3://hatch-terraform-state-<account-id> --region ap-south-1

cd infra/envs/dev
terraform init
terraform apply

Outputs include alb_dns_name, alb_listener_arn, ecs_cluster_name, subnet IDs, and security group IDs — paste these into apps/deployer/.env.


Data Model

users
  id              UUID PRIMARY KEY
  github_id       BIGINT UNIQUE NOT NULL
  github_username TEXT NOT NULL
  access_token    TEXT NOT NULL
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now()

projects
  id          UUID PRIMARY KEY
  user_id     UUID REFERENCES users(id) ON DELETE CASCADE
  repo_name   TEXT NOT NULL
  repo_url    TEXT NOT NULL
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()

deployments
  id           UUID PRIMARY KEY
  project_id   UUID REFERENCES projects(id) ON DELETE CASCADE
  branch       TEXT NOT NULL
  status       TEXT NOT NULL DEFAULT 'queued'  -- queued|building|deploying|live|failed
  cpu          INT NOT NULL
  memory_mb    INT NOT NULL
  port         INT NOT NULL
  health_check TEXT NOT NULL DEFAULT '/'
  image_uri    TEXT
  ecs_task_arn TEXT
  subdomain    TEXT UNIQUE
  url          TEXT
  created_at   TIMESTAMPTZ NOT NULL DEFAULT now()
  deployed_at  TIMESTAMPTZ

env_vars
  id            UUID PRIMARY KEY
  deployment_id UUID REFERENCES deployments(id) ON DELETE CASCADE
  key           TEXT NOT NULL
  secret_arn    TEXT NOT NULL
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now()

Real-Time Log Streaming

Builder / Deployer
  └── redis.Publish("deployment:{id}", logLine)

API — WebSocket Hub
  └── redis.Subscribe("deployment:{id}")
        └── forward each message to connected browser

Browser
  └── WebSocket client appends lines to terminal UI

Each deployment gets its own Redis channel. The hub subscribes on WebSocket connect and cleans up on disconnect or terminal deployment state.


RabbitMQ Queues

Queue Publisher Consumer
hatch.build.jobs API Builder
hatch.deploy.jobs Builder Deployer

Makefile

make dev      # docker compose up -d postgres redis rabbitmq
make up       # docker compose up (full stack with built images)
make down     # docker compose down
make build    # docker compose build
make migrate  # run pending DB migrations

Roadmap

  • PR preview environments — auto-deploy per pull request, destroyed on merge
  • Cost dashboard — real-time per-deployment cost estimates and right-sizing
  • Native runtimes — Node.js, Python, Go without a Dockerfile
  • Custom domains with automatic TLS provisioning
  • One-click rollbacks to any previous deployment
  • Built-in observability — logs, metrics, traces via OpenTelemetry

About

Hatch it. Ship it. Your Dockerfile is all you need.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors