The Recent-workloads badge and empty-state guard used workloads.length (which includes legacy kind:site rows with empty source_kind) while the list renders pluginWorkloads (source_kind != ''). With one legacy row and no real workloads the badge showed "1" over an empty list and the empty state never appeared. Both now use pluginWorkloads, matching the list and the headline Total count.
Tinyforge
Self-hosted deployment platform with a web dashboard. Deploy Docker containers from registries with zero-downtime blue-green strategy, host static sites and Deno APIs directly from Git repositories, and manage reverse proxy configuration — all from a single binary.
Features
Container Deployments
- Registry polling and webhook receiver for automatic deployments
- Blue-green deploys with health checks and automatic rollback
- Multi-stage projects (dev, staging, prod) with tag pattern matching
- Real-time deploy logs via SSE streaming
Branch Preview Environments
Get an isolated, throwaway deploy for every feature branch:
- Add a branch pattern (e.g.
feat/*) to a workload's git trigger (Triggers panel → git trigger → Branch pattern). - Pushing to any branch matching the pattern deploys an isolated per-branch preview — a child workload that inherits the source config, served at a slug-prefixed subdomain (
feat-login-app.example.com) so previews never collide with each other or the main deploy. - Previews are automatically torn down when the branch is deleted upstream.
- Manage live previews from the app's Preview environments panel (
/apps/[id]): open each branch's URL or tear it down manually. A torn-down preview is recreated on the next push to its branch.
Static Sites
Deploy static sites and Deno-powered APIs directly from Git repositories:
- Git providers: Gitea/Forgejo, GitHub, and GitLab (public and private repos)
- Static mode: Serves HTML/CSS/JS via nginx container
- Deno mode: Full-stack with TypeScript API backend + static frontend — API routes are auto-discovered from
/apifolder using a naming convention (API_get_users,API_post_items, etc.) - Markdown rendering: Optionally converts
.mdfiles to styled HTML - Branch & folder picker: Select any branch and subfolder as the deployment root
- Auto-sync: Trigger redeployment on push or tag events, or manually
- Per-site secrets: Encrypted environment variables injected at runtime
Infrastructure
- NPM / Traefik integration for automatic reverse proxy and SSL configuration
- Cloudflare DNS sync for automatic DNS record management
- Volume management: Create, browse, upload, and download Docker volumes
- Stale container cleanup: Detect and remove unused containers
- Image management: List and prune unused Docker images
- Database backups: Scheduled and manual backups with one-click restore
- Config export/import: YAML-based seed configuration for reproducible setups
Auth & Security
- Local auth with bcrypt password hashing
- OIDC/SSO support for single sign-on
- Encrypted credential storage (AES-256-GCM)
- Role-based access: Admin and user roles
Prerequisites
- Docker with Docker Compose
- A Docker network for deployed containers (e.g.
staging-net) - Nginx Proxy Manager (optional, for automatic proxy configuration)
- Wildcard DNS pointing to your server (for subdomain-based routing)
Quick Start
-
Create the Docker network (containers will be attached to this):
docker network create staging-net -
Create a
.envfile (see.env.example):cp .env.example .env # Edit .env and set ENCRYPTION_KEY and ADMIN_PASSWORD # Generate a key: openssl rand -hex 32 -
Start Tinyforge:
docker compose up -d -
Open the dashboard at
http://localhost:8080and log in withadmin/ yourADMIN_PASSWORD.
Configuration
Environment Variables
| Variable | Required | Description |
|---|---|---|
ENCRYPTION_KEY |
Yes | AES-256 key for encrypting stored credentials. Use openssl rand -hex 32 |
ADMIN_PASSWORD |
Yes (first launch) | Password for the default admin user |
SEED_FILE |
No | Path to YAML seed config (default: ./tinyforge.yaml) |
DATA_DIR |
No | SQLite database directory (default: ./data) |
LISTEN_ADDR |
No | HTTP listen address (default: :8080) |
NPM_URL |
No | Override NPM API URL (otherwise uses value from settings) |
POLLING_INTERVAL |
No | Registry polling interval, Go duration string e.g. 5m (default from settings) |
Seed Config
On first launch, Tinyforge imports a YAML seed file to pre-configure registries, projects, and settings. See tinyforge.example.yaml for the full format.
Webhook Integration
After setup, find your webhook URL at Settings > Webhook URL in the dashboard. Configure your CI/CD (Gitea Actions, GitHub Actions) to POST to this URL on image push:
curl -X POST https://your-domain/api/webhook/<secret> \
-H "Content-Type: application/json" \
-d '{"image": "registry.example.com/org/app:v1.2.3"}'
OIDC Setup
- Go to Settings > Auth in the dashboard
- Switch auth mode to OIDC
- Enter your provider's Issuer URL, Client ID, and Client Secret
- Set the Redirect URL to
https://your-domain/api/auth/oidc/callback
Development
# Build frontend
cd web && npm install && npm run build && cd ..
# Run backend (requires ENCRYPTION_KEY and ADMIN_PASSWORD env vars)
go run ./cmd/server
# Or use Make
make build
make dev
Architecture
CI/Registry --> Webhook/Poller --> Deployer --> Docker + NPM
|
Git Repo ----> Static Sites -------> Docker + NPM
|
Event Bus --> SSE --> Web Dashboard
- Backend: Go 1.24, chi router, SQLite (pure Go), Docker SDK
- Frontend: SvelteKit 2, Tailwind CSS 4, TypeScript
- Deployment: Single binary with embedded SPA, multi-stage Dockerfile