Skip to content

uzairali19/platform-stack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

platform-stack

Sanitised layout of a small self-hosted platform I run for side projects and internal tools. One Linux host. No public ingress. CI deploys over Tailscale. Prometheus and Grafana watch the lot.

Hostnames, IPs, tokens, and service names are scrubbed. The shape is real.

Where to go next

Why it exists

Most self-hosted writeups stop at docker compose up. The interesting bit is the wrapper around it: keeping the host off the public internet, shipping changes from CI without SSH'ing in by hand, and having enough signal to know what's broken without logging in.

This repo documents the wrapper, not the apps.

Who it's for

Engineers who want to run their own services on one host without reinventing the production wrapper around docker compose up. Clone it, strip what doesn't fit, replace the placeholders, keep the parts that do.

Not a framework. A reference you can steal from.

How it's built

  • Cloudflare DNS + Tunnel — public ingress without open ports on the host.
  • Traefik v3 — host-header routing to Compose-managed containers.
  • Docker Compose — one stack per service; shared Postgres + Redis.
  • GitHub Actions over Tailscale SSH — deploys without a public SSH port.
  • Prometheus + Grafana — metrics, dashboards, alerts.

Quick start

Prereqs: a Linux host with a public IP, a Cloudflare zone, a Tailscale tailnet, and a GitHub org with GHCR.

Recurring commands are wrapped in the Makefile — make help lists them.

  1. Provision the host. Install Docker. ufw default-deny on the public interface. tailscaled joins the tailnet.
  2. Create a Cloudflare Tunnel (cloudflared tunnel create platform). Drop the token in docker/traefik/.env as CLOUDFLARED_TUNNEL_TOKEN.
  3. Set Cloudflare account/zone/tunnel IDs in terraform/cloudflare/variables.tf. Run make apply.
  4. Bring up the platform: make up.
  5. Add a service — see Adding a service.

Example

A service is described by a small manifest:

# examples/example-app.yml
name: example-app
domain: app.example.com
image: ghcr.io/example-org/example-app:abc123def456
port: 8080
stack: example-app
healthcheck:
  path: /health
  interval: 10s
routing:
  internal: false
  middlewares: [secure-headers, rate-limit-default, compress]

Render it to Traefik dynamic config:

make render MANIFEST=examples/example-app.yml

Full walkthrough in Adding a service.

Repository layout

Makefile         common targets (`make help`)
docs/            ARCHITECTURE.md, OPERATIONS.md
diagrams/        mermaid sources
terraform/       Cloudflare DNS + tunnel routes
docker/          compose stacks for traefik and monitoring
github-actions/  example deploy workflow
examples/        service manifests
scripts/         deploy.sh, render-service.sh

What's intentionally omitted

Public repo, so:

  • Real domain names, hostnames, tailnet names.
  • Tunnel tokens, Cloudflare API tokens, GHCR PATs, SSH keys.
  • Real public/tailnet IPs.
  • The actual list of services and what they do.
  • Customer data, internal tool names, prod credentials.

Where you see example.com, REPLACE_ME, or xxxxxxxxxxxx, that's a placeholder.

Status

v1. One host. No Kubernetes, no service mesh, no multi-region. Those are on the "if I actually need them" list.

About

A small platform I built to run services end-to-end, handling networking, deployment, access, and observability.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors