µPaaS - lightweight app for auto rebuilding/restarting docker containers on repo changes via webhook
Go to file
sneak 5fb0b111fc Use ULID for app IDs and Docker label for container lookup
- Replace UUID with ULID for app ID generation (lexicographically sortable)
- Remove container_id column from apps table (migration 002)
- Add upaas.id Docker label to identify containers by app ID
- Implement FindContainerByAppID in Docker client to query by label
- Update handlers and deploy service to use label-based container lookup
- Show system-managed upaas.id label in UI with editing disabled

Container association is now determined dynamically via Docker label
rather than stored in the database, making the system more resilient
to container recreation or external changes.
2025-12-29 16:06:40 +07:00
cmd/upaasd Initial commit with server startup infrastructure 2025-12-29 15:46:03 +07:00
internal Use ULID for app IDs and Docker label for container lookup 2025-12-29 16:06:40 +07:00
static Initial commit with server startup infrastructure 2025-12-29 15:46:03 +07:00
templates Use ULID for app IDs and Docker label for container lookup 2025-12-29 16:06:40 +07:00
.golangci.yml Initial commit with server startup infrastructure 2025-12-29 15:46:03 +07:00
CONVENTIONS.md Initial commit with server startup infrastructure 2025-12-29 15:46:03 +07:00
Dockerfile Initial commit with server startup infrastructure 2025-12-29 15:46:03 +07:00
go.mod Use ULID for app IDs and Docker label for container lookup 2025-12-29 16:06:40 +07:00
go.sum Use ULID for app IDs and Docker label for container lookup 2025-12-29 16:06:40 +07:00
Makefile Initial commit with server startup infrastructure 2025-12-29 15:46:03 +07:00
README.md Initial commit with server startup infrastructure 2025-12-29 15:46:03 +07:00
TODO.md Update TODO.md with completed items 2025-12-29 15:51:54 +07:00

upaas

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:

  1. globals - Build-time variables
  2. logger - Structured logging
  3. config - Configuration loading
  4. database - SQLite connection + migrations
  5. healthcheck - Health status
  6. auth - Authentication service
  7. app - App management
  8. docker - Docker client
  9. notify - Notification service
  10. deploy - Deployment service
  11. webhook - Webhook processing
  12. middleware - HTTP middleware
  13. handlers - HTTP handlers
  14. server - 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.HandlerFunc allowing 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.23+
  • 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:

  1. Format: Run make fmt to format all code
  2. Lint: Run make lint and fix all errors/warnings
    • Do not disable linters or add nolint comments without good reason
    • Fix the code, don't hide the problem
  3. Test: Run make test and ensure all tests pass
    • Fix failing tests by fixing the code, not by modifying tests to pass
    • Add tests for new functionality
  4. Verify: Run make check to 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
UPAAS_PORT HTTP listen port 8080
UPAAS_DATA_DIR Data directory for SQLite and keys ./data
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 upaas-data:/data \
  upaas

License

MIT