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 Quasar: its router, its 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

Writing configuration, .env (chmod 600)

Pulling ghcr.io/aymericchaverot/quasar

Starting router · 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 routing labels is left to describe its own routing, and runs 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.

Quasar

routes it, secures it

Quasar owns 80 and 443 on the box. It writes the routing for the container, sends the hostname there, and gets the certificate from Let's Encrypt on the first request that arrives for the name — then renews it for as long as the app exists.

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 Quasar holds, next to the app that routes it. The ones nothing routes any more can be deleted — Quasar reloads its router, 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 deploy

The clone, the build and docker compose scroll past as they happen, a progress bar per step. The panel stays afterwards — it is where you read why a build failed.

Live logs

Streamed as they happen, ANSI colours preserved, severity inferred for lines that bring none. Searchable across every app.

Web terminal

A real interactive shell inside the container, in the browser. No SSH hop, no docker exec by hand.

Volume explorer

Walk what an app actually persisted — preview it, download it, upload into it, edit a file in place without changing its permissions.

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 by Quasar 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 and off the box

On demand or nightly: a consistent snapshot, each app's data/ and .env, and a database dump if you ask for one. Copied to S3-compatible storage — the copy that survives the box.

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.

Updating Quasar itself

Releases are checked every 30 minutes; a button appears in the top bar. Traefik updates the same way, and puts the old version back if the new one does not stay up.

Catalogue

62 apps, one click each.

Search it, filter it, press the one you came for. The form arrives filled in and the secrets generated — and the public address the app expects to be told about itself is worked out from your own domain.

ImmichNextcloudJellyfinPaperless-ngxAuthentikGiteaHome AssistantGrafanaVaultwardenn8nGhostPostgreSQL 17Minecraft and 49 more

Stacks, not just images

Immich, Nextcloud, Authentik, Paperless-ngx and the rest of the large ones arrive with their database and their cache, each waiting on the other — so the first deployment is not a restart loop.

Or bring your own

A catalogue is a YAML document — written here, pasted, or imported from a URL. An entry may ask a question first, a version or a port, which is what makes one of them cover a fleet rather than a server.

Stations New

Some applications deserve more than a start button.

A station is one YAML document that deploys an application and gives its page a control surface of its own — written by somebody who knows what running that particular service involves.

An example. A station somebody wrote for a game server — not something Quasar ships. Press the tabs.

Minecraft 1.21.4 (Fabric) Deployed from a station running

Players online

4 / 20

Memory

2.1 / 4 GB

Build

Fabric 0.16.9

Console

say the server is going down in 5 Send

Sent over RCON, inside this server's own container.

Below this block: Build, Routing, TLS, Storage, Environment — Quasar's own, untouched.

It is still an ordinary application

The same containers, logs, storage explorer, backups and TLS as anything else on the dashboard. Remove the station and a perfectly normal application is left running.

It can only do what it declared

Every privileged thing is enumerated, named — hosts, paths, services, keys — and shown to you in plain words before anything is installed. A revision that asks for more waits until you accept it.

It cannot take the dashboard with it

The script runs in a disposable process holding no socket, no disk and no network, bounded in time and memory from outside. When one breaks, it is the only thing that breaks.

Security

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

The socket is never mounted

Quasar never hands the dashboard the Docker socket. It reaches Docker through a proxy of its own, opened to 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. Sessions are listed and revocable.

A login page in front of an app

Password protection is a page Quasar serves, not the grey box the browser draws. Switched on per app, with a session of that app's own — and nothing to add inside the app.

Rate limit and allowlist

Per app, at the proxy: requests per second, an allowlist of addresses, hardening headers. Quasar checks a CIDR itself — a malformed one makes the proxy drop the rule, and fail open.

Read-only host mount

The host's / is mounted read-only: disk metrics, the certificate store, and what the volume explorer shows. Writing into Docker's own volumes stays off until you turn it on.

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 do not go through Quasar, 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