Your own platform, on your own server

From a bare server to production in one command.

Quasar turns a blank Linux box into a platform: an admin dashboard on admin.your-domain.com, and every app you deploy on its own subdomain with a certificate it issues itself.

root shell on the server
curl -sSL https://raw.githubusercontent.com/AymericChaverot/quasar/main/setup.sh | sudo bash
Dashboard RAM
< 50 MB
Runtime deps
1 binary
Ways to deploy
3 modes
Whole setup
4 questions

Installation

Four questions, then a platform.

The script installs Docker if it is missing, asks for your root domain, a Let's Encrypt email, and the admin account it should create. Then it clones into /opt/quasar, writes the configuration, and brings up Traefik, the socket proxy and the dashboard.

Nothing to configure afterwards. The first request to admin.your-domain.com is what triggers certificate issuance, which is why the first load takes a few seconds and every one after it does not.

root@server live

curl -sSL .../setup.sh | sudo bash

Docker not found — installing

Root domain example.com

Let's Encrypt email ops@example.com

Admin username admin

Admin password ••••••••••••

Cloning into /opt/quasar

Generating traefik.yml, .env (chmod 600)

Pulling ghcr.io/aymericchaverot/quasar

Starting traefik · socket-proxy · dashboard

Platform up in 94s

https://admin.example.com

Deployment

Three ways to ship.

All three end in the same place: a container on the network, a subdomain routed to it, a certificate issued for it.

Docker image

Public, or from a private registry. Give it an image and an internal port; Quasar does the rest.

Git build

A repo with a Dockerfile or a compose file. Quasar clones it, detects which one describes the app, and builds. Private repos authenticate with a scoped token.

Injected compose

Paste a docker-compose.yml and Quasar runs the stack — after rewriting it to sit behind the proxy.

Compose is detected, not guessed

A repo carrying a compose file at its root is run as a stack, not built from its Dockerfile — a Dockerfile sitting next to it usually describes just one service of that stack. The app's page shows what was detected and lets you switch to the Dockerfile explicitly when the compose file is only there for local development.

Redeploy is not Update

Redeploy recreates the container from what is already on the server — same image, same commit. It is what applies a configuration change. Update fetches the new version first: git pull and rebuild, docker pull, or docker compose pull. A per-app webhook URL runs the second one on every push.

Compose

Your compose file, adapted on the way in.

An ordinary docker-compose.yml — the one that runs as-is on a laptop, with its own nginx bound to port 80 — cannot work behind a proxy that already owns 80 and 443. Quasar rewrites it instead of asking you to.

docker-compose.yml · yours, untouched
services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    depends_on: [api]
  api:
    build: .
    expose: ["8000"]
docker-compose.quasar.yml · generated
services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"    networks: [default, traefik-net]
    labels:
      traefik.enable: "true"
      traefik.http.routers.blog.rule: Host(`blog.example.com`)
      traefik.http.routers.blog.tls.certresolver: le
      traefik.http.services.blog.loadbalancer.server.port: "80"    depends_on: [api]
  api:
    build: .
    expose: ["8000"]
networks:
  traefik-net: { external: true }

The repo is never modified

The rewrite is written to a docker-compose.quasar.yml next to the original, regenerated on every deploy. A file that already carries its own traefik.* labels is run exactly as it is.

The front service is found, not guessed

No service name or image name is ever consulted. In order: whoever published host port 80/443, else whoever the rest of the stack lines up behind via depends_on, else the only one offering the app's configured port, else the only service in the file.

When nothing decides, nothing happens

If none of those rules resolves, Quasar leaves the file alone rather than routing your domain at random, and the Routing panel asks you to pick. YAML anchors and merge keys are flattened first — otherwise the labels would land on a shared anchor.

Routing & TLS

A subdomain and a port. That is the whole configuration.

You give

blog.example.com

port 3000

Plus any extra hostnames the app should answer for — www.myblog.com included.

Traefik v3

router + service labels

Generated onto the container. The certificate is issued by Let's Encrypt on the first request that arrives for the name.

Your app

https://blog.example.com

Reached over TLS, with whatever protections the app has configured — basic auth, resource limits, healthchecks.

Per-app TLS status, with a diagnosis

Every hostname an app serves reports its certificate state, and when one is missing the page says why: the name does not resolve, or it resolves somewhere other than this server. That second case — a registrar's leftover A record — is the usual reason an app has no HTTPS while everything looks correct.

Certificates, listed and reclaimable

Every certificate Traefik holds, next to the app that routes it. The ones nothing routes any more can be deleted — Traefik restarts, which costs a few seconds.

Operations

Deploying is the easy half.

What you actually spend time on is the week after: reading logs, getting a shell, finding out what filled the disk. All of it is in the dashboard.

Live logs

Streamed over SSE, ANSI colours preserved, severity inferred for lines that bring none. Searchable across every app.

Web terminal

An interactive shell inside the container — xterm.js over a WebSocket. No SSH hop, no docker exec by hand.

Tasks

Commands run inside a container, on demand or every N minutes. Output and exit status are kept.

Metrics with history

CPU, memory and disk for the host and per app, sampled into SQLite and drawn as 24-hour sparklines rendered server-side.

Healthchecks

A periodic HTTP probe, automatic restart after three failures, and an availability history you can read afterwards.

History and rollback

Every deployment logged with its source, image, duration and result. The last four built images are kept — rollback is one click.

Backups

On demand or nightly: a consistent SQLite snapshot plus each app's data/ and .env. Configurable retention, downloadable, restorable in one click.

Disk maintenance

Reclaimable space is costed per category before you press anything, then swept. Stopped apps, their images and each app's rollback targets are spared.

Notifications

A Discord/Slack-compatible webhook for failed deploys, apps entering error, recoveries and failed backups.

One-click catalogue

Common services with the form already filled in and secrets generated for you.

PostgreSQLMySQLRedisUptime KumaGhostn8nVaultwarden

Security

What it is careful about, and what it is not.

The socket is never mounted

The dashboard talks to Docker only through docker-socket-proxy, with the API sections it needs and no others. EXEC is required by the web terminal and tasks — drop it if you use neither.

Scoped, encrypted Git tokens

Each token declares a scope — a forge, an organisation, a single repo, or * as a fallback — and the narrowest one wins. Encrypted with the master key, never shown again, testable with a real git ls-remote.

Sessions and 2FA

HTTP-only, Secure, SameSite=Lax cookies; bcrypt passwords; TOTP two-factor with a QR enrolment. Basic auth can be switched on per app at the proxy.

Read-only host mount

The host's / is mounted read-only, and only so disk metrics have something to measure.

One thing worth knowing. A compose stack that publishes host ports other than 80/443 keeps them — a stack may legitimately want to expose a database or a game server. Those ports bypass Traefik, and therefore bypass TLS and the app's configured protections. The Routing panel flags them rather than removing them silently.

Take a server. Give it ten minutes.

Point a domain and a wildcard at it, open 22, 80 and 443, then run this as root.

root shell on the server
curl -sSL https://raw.githubusercontent.com/AymericChaverot/quasar/main/setup.sh | sudo bash