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.
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
| 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 |
| Package | Purpose |
|---|---|
packages/db |
sqlc-generated type-safe database layer + SQL migrations |
packages/config |
Environment variable loader |
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
| 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 |
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
- Go 1.23+
- Node.js 20+
- Docker + Docker Compose
- AWS CLI configured with IAM credentials
- Terraform 1.5+
git clone https://github.com/YHQZ1/Hatch.git
cd Hatch
cd apps/web && npm install && cd ../..
go work synccp 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.example apps/web/.env.localapps/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-buildsapps/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=disablemake dev # starts Postgres, Redis, RabbitMQ via Docker Composemigrate -path packages/db/migrations \
-database "postgres://hatch:hatch@localhost:5432/hatch?sslmode=disable" up# 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 devOpen http://localhost:3000.
# 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 applyOutputs include alb_dns_name, alb_listener_arn, ecs_cluster_name, subnet IDs, and security group IDs β paste these into apps/deployer/.env.
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()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.
| Queue | Publisher | Consumer |
|---|---|---|
hatch.build.jobs |
API | Builder |
hatch.deploy.jobs |
Builder | Deployer |
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 migrationsContributions are welcome β bug fixes, features, docs, and everything in between. Please read CONTRIBUTING.md before opening a PR. It covers local setup, branch conventions, commit style, and the PR process.
For security vulnerabilities, see SECURITY.md β please don't use public issues for security reports.
- 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
MIT β see LICENSE.