µPaaS - lightweight app for auto rebuilding/restarting docker containers on repo changes via webhook
|
|
||
|---|---|---|
| cmd/upaasd | ||
| internal | ||
| static | ||
| templates | ||
| .golangci.yml | ||
| CONVENTIONS.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
µPaaS by @sneak
A simple self-hosted PaaS that auto-deploys Docker containers from Git repositories via Gitea webhooks.
Features
- Single admin user with argon2id password hashing
- Per-app SSH keypairs for read-only deploy keys
- Per-app UUID-based webhook URLs for Gitea integration
- Branch filtering - only deploy on configured branch changes
- Environment variables, labels, and volume mounts per app
- Docker builds via socket access
- Notifications via ntfy and Slack-compatible webhooks
- Simple server-rendered UI with Tailwind CSS
Non-Goals
- Multi-user support
- Complex CI pipelines
- Multiple container orchestration
- SPA/API-first design
- Support for non-Gitea webhooks
Architecture
Project Structure
upaas/
├── cmd/upaasd/ # Application entry point
├── internal/
│ ├── config/ # Configuration via Viper
│ ├── database/ # SQLite database with migrations
│ ├── docker/ # Docker client for builds/deploys
│ ├── globals/ # Build-time variables (version, etc.)
│ ├── handlers/ # HTTP request handlers
│ ├── healthcheck/ # Health status service
│ ├── logger/ # Structured logging (slog)
│ ├── middleware/ # HTTP middleware (auth, logging, CORS)
│ ├── models/ # Active Record style database models
│ ├── server/ # HTTP server and routes
│ ├── service/
│ │ ├── app/ # App management service
│ │ ├── auth/ # Authentication service
│ │ ├── deploy/ # Deployment orchestration
│ │ ├── notify/ # Notifications (ntfy, Slack)
│ │ └── webhook/ # Gitea webhook processing
│ └── ssh/ # SSH key generation
├── static/ # Embedded CSS/JS assets
└── templates/ # Embedded HTML templates
Dependency Injection
Uses Uber fx for dependency injection. Components are wired in this order:
globals- Build-time variableslogger- Structured loggingconfig- Configuration loadingdatabase- SQLite connection + migrationshealthcheck- Health statusauth- Authentication serviceapp- App managementdocker- Docker clientnotify- Notification servicedeploy- Deployment servicewebhook- Webhook processingmiddleware- HTTP middlewarehandlers- HTTP handlersserver- HTTP server
Request Flow
HTTP Request
│
▼
chi Router ──► Middleware Stack ──► Handler
│
(Logging, Auth, CORS, etc.)
│
▼
Handler Function
│
▼
Service Layer (app, auth, deploy, etc.)
│
▼
Models (Active Record)
│
▼
Database
Key Patterns
- Closure-based handlers: Handlers return
http.HandlerFuncallowing one-time initialization - Active Record models: Models encapsulate database operations (
Save(),Delete(),Reload()) - Async deployments: Webhook triggers deploy via goroutine with
context.WithoutCancel() - Embedded assets: Templates and static files embedded via
//go:embed
Development
Prerequisites
- Go 1.25+
- golangci-lint
- Docker (for running)
Commands
make fmt # Format code
make lint # Run comprehensive linting
make test # Run tests with race detection
make check # Verify everything passes (lint, test, build, format)
make build # Build binary
Commit Requirements
All commits must pass make check before being committed.
Before every commit:
- Format: Run
make fmtto format all code - Lint: Run
make lintand fix all errors/warnings- Do not disable linters or add nolint comments without good reason
- Fix the code, don't hide the problem
- Test: Run
make testand ensure all tests pass- Fix failing tests by fixing the code, not by modifying tests to pass
- Add tests for new functionality
- Verify: Run
make checkto confirm everything passes
# Standard workflow before commit:
make fmt
make lint # Fix any issues
make test # Fix any failures
make check # Final verification
git add .
git commit -m "Your message"
The Docker build runs make check and will fail if:
- Code is not formatted
- Linting errors exist
- Tests fail
- Code doesn't compile
This ensures the main branch always contains clean, tested, working code.
Configuration
Environment variables:
| Variable | Description | Default |
|---|---|---|
PORT |
HTTP listen port | 8080 |
UPAAS_DATA_DIR |
Data directory for SQLite and keys | ./data |
UPAAS_HOST_DATA_DIR |
Host path for DATA_DIR (when running in container) | same as DATA_DIR |
UPAAS_DOCKER_HOST |
Docker socket path | unix:///var/run/docker.sock |
DEBUG |
Enable debug logging | false |
SENTRY_DSN |
Sentry error reporting DSN | "" |
METRICS_USERNAME |
Basic auth for /metrics | "" |
METRICS_PASSWORD |
Basic auth for /metrics | "" |
Running with Docker
docker run -d \
-p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /path/on/host/upaas-data:/var/lib/upaas \
-e UPAAS_HOST_DATA_DIR=/path/on/host/upaas-data \
upaas
Important: When running µPaaS inside a container, set UPAAS_HOST_DATA_DIR to the host path
that maps to UPAAS_DATA_DIR. This is required for Docker bind mounts during builds to work correctly.
Session secrets are automatically generated on first startup and persisted to $UPAAS_DATA_DIR/session.key.
License
WTFPL