Docker image
Public, or from a private registry. Give it an image and an internal port; Quasar does the rest.
Your own platform, on your own server
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.
curl -sSL https://raw.githubusercontent.com/AymericChaverot/quasar/main/setup.sh | sudo bash
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.
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
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.
Public, or from a private registry. Give it an image and an internal port; Quasar does the rest.
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.
Paste a docker-compose.yml and Quasar runs the stack — after rewriting it to sit behind the proxy.
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 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.
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.
services:
web:
image: nginx:alpine
ports:
- "80:80"
depends_on: [api]
api:
build: .
expose: ["8000"]
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 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.
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.
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.
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.
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.
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.
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.
Streamed over SSE, ANSI colours preserved, severity inferred for lines that bring none. Searchable across every app.
An interactive shell inside the container — xterm.js over a WebSocket. No SSH hop, no docker exec by hand.
Commands run inside a container, on demand or every N minutes. Output and exit status are kept.
CPU, memory and disk for the host and per app, sampled into SQLite and drawn as 24-hour sparklines rendered server-side.
A periodic HTTP probe, automatic restart after three failures, and an availability history you can read afterwards.
Every deployment logged with its source, image, duration and result. The last four built images are kept — rollback is one click.
On demand or nightly: a consistent SQLite snapshot plus each app's data/ and .env. Configurable retention, downloadable, restorable in one click.
Reclaimable space is costed per category before you press anything, then swept. Stopped apps, their images and each app's rollback targets are spared.
A Discord/Slack-compatible webhook for failed deploys, apps entering error, recoveries and failed backups.
Common services with the form already filled in and secrets generated for you.
What it is careful about, and what it is not.
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.
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.
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.
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.
Point a domain and a wildcard at it, open 22, 80 and 443, then run this as root.
curl -sSL https://raw.githubusercontent.com/AymericChaverot/quasar/main/setup.sh | sudo bash