µPaaS - lightweight app for auto rebuilding/restarting docker containers on repo changes via webhook
Go to file
clawbot 867cdf01ab fix: add ownership verification on env var, label, volume, and port deletion
Verify that the resource's AppID matches the URL path app ID before
allowing deletion. Without this check, any authenticated user could
delete resources belonging to any app by providing the target resource's
ID in the URL regardless of the app ID in the path (IDOR vulnerability).

Closes #19
2026-02-15 21:02:46 -08:00
cmd/upaasd Initial commit with server startup infrastructure 2025-12-29 15:46:03 +07:00
internal fix: add ownership verification on env var, label, volume, and port deletion 2026-02-15 21:02:46 -08:00
static rewrite log viewer panes: smart auto-scroll with follow button 2026-02-15 20:48:43 -08:00
templates Merge branch 'main' into fix/issue-11 2026-02-16 05:51:25 +01:00
.golangci.yml Add deployment improvements and UI enhancements 2025-12-30 15:05:26 +07:00
CONVENTIONS.md Initial commit with server startup infrastructure 2025-12-29 15:46:03 +07:00
docker-compose.yml Use PORT instead of UPAAS_PORT for listen port 2025-12-29 16:15:05 +07:00
Dockerfile Install golangci-lint v2 in Docker build 2025-12-29 16:29:16 +07:00
go.mod Add CSRF protection to state-changing POST endpoints 2026-02-15 14:17:55 -08:00
go.sum Add CSRF protection to state-changing POST endpoints 2026-02-15 14:17:55 -08:00
LICENSE Add WTFPL license 2025-12-29 16:25:22 +07:00
Makefile Integrate Alpine.js for reactive UI 2026-01-01 05:37:46 +07:00
README.md Add deployment improvements and UI enhancements 2025-12-30 15:05:26 +07:00
TODO.md Update TODO.md with completed items 2025-12-29 15:51:54 +07:00

µ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:

  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.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:

  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
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