# quick: full documentation > Self-hosted static hosting for teams. Publish a folder with one command and get name.yourdomain behind your company sign-in. --- # Getting started quick is static hosting for a team. You publish a folder from your terminal and it goes live at `https://.`. By default only people who can sign in with your company account can open it. You need a quick server. If your company already runs one, ask for its address (for example `quick.acme.com`). If not, see [Self-hosting](/docs/self-hosting). ## Install the CLI ```bash # macOS and Linux curl -fsSL https://quick-cli.com/install.sh | sh ``` ```powershell # Windows (PowerShell) irm https://quick-cli.com/install.ps1 | iex ``` The script downloads the prebuilt binary from the latest [GitHub release](https://github.com/zupolgec/quick/releases) into `~/.local/bin` (`%LOCALAPPDATA%\quick` on Windows). On macOS and Linux, set `QUICK_INSTALL_DIR` to put it somewhere else. Every quick server also serves the same scripts at `https:///install.sh`. With Go installed you can build it instead: ```bash go install github.com/zupolgec/quick/cmd/quick@latest ``` Update later with `quick upgrade` (`quick upgrade --check` only checks). ## Sign in ```bash quick login --server quick.acme.com ``` Your browser opens your company sign-in page. When you are back, the CLI remembers the login and makes this server the default. You can give the server as a bare domain or a full URL. ## Publish a folder ```bash quick deploy launch ./dist ``` ``` ✓ Authenticated as giulia About to publish https://launch.quick.acme.com 3 files, 879.0 KB (excluded by: default rules) Proceed? [y/N]: y sending 879.0 KB of 879.0 KB ✓ uploaded 879.0 KB of 879.0 KB ✓ launch published → https://launch.quick.acme.com ``` That is the whole loop. A few things worth knowing: - **A deploy is a mirror.** The folder replaces the whole site: files you removed disappear from it. - **The folder remembers.** The first deploy writes a `.quick` file with the site name, the server and the folder. From then on `quick deploy` with no arguments republishes it. - **Redeploys are incremental.** Only the parts of files the server does not have yet are uploaded. - **New sites are instant.** There is nothing to configure per site. ## Next - [CLI reference](/docs/cli): every command and flag. - [Site conventions](/docs/sites): `404.html`, SPAs, `_redirects`, `_headers`, search engines, what is never uploaded. - [Access and safety](/docs/access): public sites, access codes, locks, rollback. - [CI and agents](/docs/ci-and-agents): deploy tokens, GitHub Actions, the agent skill. --- # CLI reference `` is optional inside a folder with a `.quick` file, which the first deploy writes: the name and the server come from there, so `quick publish`, `quick rollback` or `quick domains add www.example.com` act on that site. Outside such a folder, `quick deploy` uses the folder's name and the other commands ask for the site. | Command | What it does | |---|---| | `quick` | Overview: server, login, linked site, and the command list | | `quick status` | Real visibility and lock of the site, and what a deploy would upload | | `quick login` | Sign in to a server; it becomes the default | | `quick servers` | Known servers and their login state | | `quick servers use ` | Change the default server | | `quick deploy [] [folder]` | Publish a folder (default: the current one) | | `quick ignore [folder]` | Write an editable `.quickignore` with the defaults inside | | `quick access []` | Who can see the site: level, owner, and the people on the list (if you are on it) | | `quick access [] team \| only-me \| public \| code [--code X] [--code-only]` | Everyone at the company (the default), only you, anyone, or an access code (colleagues get in without it, unless `--code-only` on a locked site) | | `quick access [] add\|remove ...` | Share with chosen people, or stop (people outside the company sign in with an email link, if the server sends email) | | `quick publish []` | Open the site to anyone, no sign-in | | `quick unpublish []` | Put it back behind the company sign-in (the default) | | `quick private [] [--code X]` | Require an access code (generated if you don't give one) | | `quick lock []` / `quick unlock []` | Only you can overwrite it / anyone can again | | `quick token create [] --name [--expires 90d]` | Create a deploy token for one site | | `quick token list []` | List its deploy tokens | | `quick token revoke [] ` | Revoke one | | `quick domains []` | Custom domains and the DNS records still missing | | `quick domains add [] ` | Attach a custom domain | | `quick domains rm [] ` | Detach it | | `quick limit [] [\|default]` | Show the site's size limit; administrators change it (e.g. `1.5GB`) | | `quick rollback []` | Swap back to the previous version (run it again to undo) | | `quick delete []` | Delete the site and its settings, for good | | `quick skill` | Install the agent skill (see [CI and agents](/docs/ci-and-agents)) | | `quick upgrade [--check]` | Update the CLI to the latest release | ## Deploy flags | Flag | Effect | |---|---| | `--yes` | Skip the confirmation. Without it, a non-interactive deploy is refused. | | `--dry-run` | Show what would be uploaded and stop. | | `--force` | Allow publishing an empty folder, which empties the site. | | `--takeover` | Take over from a deploy in progress (also on `rollback`). | | `--public` / `--private[=code]` | Set the visibility right after the deploy. | | `--server ` | Use this server for this command. | Before publishing, the CLI prints who you are, where the site goes and how much it holds, then asks. If the last deploy of that site was by someone else, it asks you to type the site name before overwriting their work. ## Choosing the server You can use several quick servers from one machine, one per company for example. Each keeps its own login. The server is picked in this order: 1. `--server` 2. the `QUICK_SERVER` environment variable 3. the folder's `.quick` file 4. the default server (the last one you logged in to, or the one set with `quick servers use`) A server you have used before can be named by a word of its address: `--server acme` means `quick.acme.com`. A new server needs its full address the first time. The CLI also tries `deploy.` and remembers whichever answers. If the folder is linked to a site on another server, or to a different site, the CLI says so and asks before going on. ## Files the CLI writes - `.quick`, in the folder you deploy from: site name, server and published sub-folder. It is never uploaded. - Known servers and logins, in your user config folder: `~/Library/Application Support/quick/` on macOS, `~/.config/quick/` on Linux, `%AppData%\quick\` on Windows. --- # Site conventions quick follows the conventions most static hosts use. There is no configuration file: everything is a file in your folder. ## URLs - `index.html` is the index of its folder. - `/about` serves `about.html`; `about/index.html` is served at `/about/`. - HTML pages have one canonical address: `/about.html` and `/about/index.html` redirect to `/about`, and `/about` redirects to `/about/` when it is a folder. ## Not found and single-page apps - `404.html` is shown, with status 404, for paths that don't exist. It can sit at the root or in any folder: the one nearest to the missing path wins. - `200.html` at the root is the app shell of a single-page app: it is served, with status 200, for every route that is not a file. - Without `200.html`, a missing path is a real 404. There is no silent fallback to the home page. ## `_redirects` A `_redirects` file at the root of the site defines redirects, rewrites and proxies, one rule per line, in the Netlify style: ``` /old /new 301 # redirect (302, 307, 308 too; 301 if omitted) /* /index.html 200 # rewrite: catch-all for a single-page app /api/* https://api.example.com/:splat 200 # proxy: call an API from the same origin ``` - Rules apply only when no file matches the request (files always win), and before `200.html`. - The first matching rule wins. Invalid lines are ignored. - The only wildcard is a trailing `/*`, available as `:splat` in the destination. The query string is kept. - A proxy passes requests through to a public `https` host: never to private or internal addresses, never to other quick sites, and no credentials are added. - Proxied paths follow the site's visibility: a site behind sign-in has its API behind sign-in too. - Your visitors' quick sign-in never reaches the proxied host, and cookies it sets stay on your site's address. - The `_redirects` file itself is never served. Changes take effect within a few seconds. ## `_headers` A `_headers` file at the root of the site sets response headers per path, in the Netlify style: a path, then the headers indented under it. ``` /* X-Frame-Options: DENY /assets/* Cache-Control: public, max-age=31536000, immutable Access-Control-Allow-Origin: * ``` - Paths are the ones in the address bar: `/about`, not `/about.html`. The only wildcard is a trailing `/*`. - Every matching rule applies. A header set by several rules gets its values joined with `, `. - Headers apply to the site's files, `200.html` and `404.html` included. Redirects and proxied responses don't get them. - Headers quick manages itself can't be set, and their line is ignored: cookies (`Set-Cookie`), sign-in (`WWW-Authenticate`, `Authorization`, `Proxy-*`), how a file is sent (`Content-Type`, `Content-Encoding`, `Content-Length`, `Content-Range`, `Transfer-Encoding`, `ETag`, `Last-Modified`, `Accept-Ranges`, `Location`), security (`X-Content-Type-Options`, `Strict-Transport-Security`, `Access-Control-Allow-Credentials`, `Clear-Site-Data`), `Alt-Svc`, `X-Quick-*` and `X-Forwarded-*`. - The `_headers` file itself is never served. Changes take effect within a few seconds. ## Search engines Sites on a quick address are drafts and internal work, so they stay out of search results: every response on `.` carries `X-Robots-Tag: noindex, nofollow`, pages as well as images, fonts and scripts. - It can't be turned off. An `X-Robots-Tag` in `_headers` adds directives on top of it. - A site that should be found goes on a [custom domain](/docs/custom-domains), where quick adds nothing. - Don't block crawlers with `Disallow: /` in `robots.txt`: a crawler that can't fetch a page never sees its noindex and may still list the bare address. ## What is never uploaded Exclusions are decided in three tiers: 1. **Security, always.** Hidden files (`.git`, `.env`, `.quick` and so on, except `.well-known`) and secrets (`*.pem`, `*.key`, `id_rsa`, keystores). This tier cannot be overridden. 2. **Convenience, by default.** `node_modules/`, `vendor/`, `*.log` and temporary files. 3. **`.quickignore`, if present.** It replaces tier 2. Gitignore syntax, with `!` to bring a file back. `quick ignore` writes one with the defaults already in it. Run `quick status` or `quick deploy --dry-run` to see what is in and what is out. A folder named `__quick/` at the root is refused: that path belongs to quick. ## Precompressed files and Unity WebGL `x.js.br`, `x.wasm.gz`, `x.data.br`, `x.json.gz` (Brotli `.br` or gzip `.gz`) are served with the type of the inner file and the right `Content-Encoding`, so the browser decompresses them itself. A Unity WebGL build deploys as it is, with any compression setting: Brotli, Gzip or "decompression fallback" `.unityweb`: when that file is compressed, quick serves it with the right `Content-Encoding` too, which starts faster than Unity's own loader. Other `.gz` files, such as archives, stay normal downloads. Builds compressed as a whole change everywhere on every build, so they gain little from incremental deploys. Publish them uncompressed if upload time matters more than file size. ## Size limit A site can take up to 100 MB per version, unless the server sets another default. Bigger sites, such as game builds with video, need an administrator to raise that site's limit (`quick limit 1.5GB`, or the site's page in the dashboard). `quick limit ` shows the current one and who the administrators are. A deploy over the limit is refused before anything changes on the site, and the message says who to ask. ## Incremental deploys The CLI splits files into content-defined chunks of about 64 KiB and uploads only the chunks the server doesn't already have in the published version. Changing part of a large file sends a few chunks; a renamed file sends nothing. The CLI reports what it really sent, for example `uploaded 9.2 KB of 3.1 MB`. Against an older server, or a busy one, it uploads the whole folder. ## `quick.js` Every site can import a small library from `/__quick/quick.js` (an ES module; types at `/__quick/quick.d.ts`). ```html ``` --- # Access and safety ## Who can open a site Every site has one of these visibilities. Changing it is instant. ```bash quick access launch # who can see it now quick access launch team # everyone at the company (the default) quick access launch only-me # only you quick access launch add giulia@acme.com # you and the people you choose quick access launch remove giulia@acme.com quick access launch code [--code abc12345] # access code, generated if you don't give one quick access launch code --code-only # colleagues need the code too (locked sites) quick access launch code --code-only=false # colleagues get in, whatever the server's default quick access launch public # anyone, no sign-in ``` Inside a folder linked to the site (it has a `.quick` file) you can drop the name: `quick access only-me`, `quick access add giulia@acme.com`. - **Everyone at the company** checks the visitor's session with your identity provider (Google Workspace or any OIDC provider). Only accounts in the allowed email domains get in. - **Chosen people** works like company sign-in, but only the owner (whoever created the site) and the accounts on the list get in. Anyone else at the company sees a page saying the site is restricted and who owns it, never who else is on the list. "Only me" is the same with an empty list. The list takes company accounts, and people outside the company too when the server can send email (see below). - **People outside the company** on a chosen-people list can't use company sign-in. On the sign-in page they enter their email and get a link: it works once, for 15 minutes, and keeps them signed in on that browser for 7 days. Only listed addresses get the email, and the page answers the same way for anyone, so it doesn't reveal who is on the list. Take someone off the list and they're out at once. This needs the server to send email (`QUICK_SMTP_URL`); without it, `quick access add` refuses addresses outside the company. - **Access code** shows a page asking for the code. Once entered, it is remembered for 7 days on that browser. Codes have at least 8 characters. When you set the code, quick also prints a link that carries it (`https://launch.quick.acme.com/?quick_code=...`): whoever opens it gets straight in, without typing anything, and the code leaves the address bar. Whoever can change the site sees the current code and link again with `quick access launch` and on the site's page in the dashboard. Changing the code signs out everyone who entered the old one. After 10 wrong codes in 15 minutes, a visitor has to wait before trying again. - **Colleagues and access codes**: the code is for people outside the company. Anyone signed in with a company account opens a code site without it, and the code page offers them a link to sign in. To ask colleagues for the code too, add `--code-only`, or tick "Ask colleagues for the code too" on the site's page in the dashboard. It needs a site colleagues can't manage, otherwise any of them could read or change the code anyway: in `free` mode (the default) a locked site (`quick lock launch` first), and unlocking it turns the option off; in `shared` and `owned` mode, any site with a recorded owner. Administrators always get in. Setting a code again without the flag applies the server's default (see below); `--code-only=false` lets colleagues in whatever the default. `quick private` and `deploy --private` say which of the two applies. - **Public** serves the site to everyone. Administrators (set on the server with `QUICK_ADMINS`) can see, open and manage every site, chosen-people and "only me" ones included, and can do on any site whatever its owner can: change who sees it, lock or unlock it, delete it, manage its tokens and domains, raise its size limit. The site's page in the dashboard names them. If the settings can't be read, quick refuses a site's first publish and a code set without an explicit choice, rather than guessing a default that could leave the site more open. Administrators also have a **Settings** page in the dashboard with the server's defaults. They apply when nobody chooses otherwise and never change existing sites: - **Lock new sites**: the first publish locks a site for whoever created it. - **Ask colleagues for the access code too**: a site given an access code asks colleagues for it as well. It still holds only on sites colleagues can't manage, as above. To let colleagues in on one site, use `quick access launch code --code-only=false` or untick the box on its page. `quick publish`, `quick unpublish` and `quick private` still work as shortcuts for `public`, `team` and `code`. The same choices are on the site's page in the dashboard; there, leaving the code field empty generates a random code. ## Who can change a site quick records who created each site and who published it last. A site exists from its first publish, so every site has an owner: before that, quick refuses to change who can see it, lock it or create deploy tokens for it. Only an administrator's size limit can be set ahead, for a big first publish. The server decides what owning means with `QUICK_OWNERSHIP`: | Mode | Overwrite | Delete, change visibility | |---|---|---| | `free` (default) | anyone in the company | anyone in the company | | `shared` | anyone in the company | the creator only | | `owned` | the creator only | the creator only | In any mode, the owner can lock a site: ```bash quick lock launch # from now on only you can overwrite or change it quick unlock launch ``` A site limited to chosen people is stricter in every mode: only its owner changes who can see it or deletes it. Deploys follow the rules above, so on an unlocked site a colleague may publish to it without being able to open it; lock it if that matters. Such a colleague, and any deploy token, always uploads the whole folder instead of only the changes, and the site's tokens and custom domains stay hidden from people who can't open it. When the last deploy of a site was by someone else, the CLI asks you to type the site name before replacing their work. ## One deploy at a time While someone publishes a site, another deploy of the same site (yours from another terminal too) and any rollback are refused with a message saying who and since when: ``` mario@… is publishing this site (since 14:02); retry in a moment or use --takeover ``` - The site is free again as soon as that deploy ends. - A deploy you interrupt with Ctrl-C, or that fails, frees it immediately. One whose CLI died or lost the network frees it after two minutes without activity. - A deploy interrupted before publishing publishes nothing. If publishing had already started it may still complete; the CLI says so, so check the site before running it again. - `--takeover` stops the other deploy without publishing (unless it was already publishing: then it finishes and yours publishes right after). Use it when you know the other deploy is stale, not by reflex. - Deleting a site always works and ends a deploy in progress. ## Rollback ```bash quick rollback launch ``` Every deploy keeps the previous version. `rollback` swaps the two, so a second rollback restores what you just undid. Publishing is atomic, on local disk and on S3 alike: the new version is written aside and becomes visible all at once. Visitors see the old version or the new one, never a mix. A deploy that fails leaves the site as it was. ## Delete ```bash quick delete launch ``` This removes the files and the settings of the site and cannot be undone. The CLI asks for confirmation; for a public or code-protected site you type its name. --- # CI and agents ## Deploy tokens CI runners and hosted agents (GitHub Actions, Claude Code on the web, other sandboxes) can't open a browser to sign in. Give them a deploy token instead: ```bash quick token create launch --name github-actions --expires 90d # the value is printed once: save it as a secret named QUICK_API_TOKEN QUICK_API_TOKEN=qk_... quick deploy launch ./dist --yes ``` The site has to exist first: publish it once yourself (`quick deploy launch ./dist`), so it gets its owner, then create the token. A token is deliberately narrow: - It is valid for **one site**, and only for **deploys**. It cannot delete, roll back, change visibility or the lock, or create other tokens. - Expiry is `30d`, `90d` (default), `180d`, `365d` or `never`. - Up to 20 tokens per site, names up to 40 characters. - The server stores only a keyed hash; the value is shown once. `quick token list launch` shows token IDs; `quick token revoke launch ` removes one. The dashboard on your server (open the site, then Deploy tokens) does the same in the browser. In `shared` and `owned` modes, managing tokens follows the same rules as changing the site's visibility. ## GitHub Actions ```yaml name: deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci && npm run build - run: curl -fsSL https://quick-cli.com/install.sh | sh - run: ~/.local/bin/quick deploy launch ./dist --server quick.acme.com --yes env: QUICK_API_TOKEN: ${{ secrets.QUICK_API_TOKEN }} ``` ## The agent skill ```bash quick skill # ~/.claude/skills/quick/SKILL.md quick skill --all # Claude Code, Codex and Gemini quick skill --target codex --project # .codex/skills/quick/ in this repository ``` `quick skill` installs a `SKILL.md` that teaches a coding agent how to use the CLI: deploys as mirrors, confirmation and `--yes`, visibility, tokens, `_redirects`, and when not to use `--takeover`. `SKILL.md` is an open format read by Claude Code, Codex, Gemini, Cursor and others. When you upgrade the CLI, skills you already installed for Claude, Codex or Gemini are refreshed on the next command. New ones are never created without `quick skill`. Set `QUICK_NO_SKILL_AUTOUPDATE=1` to turn the refresh off. The current skill is published at [quick-cli.com/skill.md](/skill.md). ## These docs, for agents - [/llms.txt](/llms.txt) lists every page with a one-line summary. - [/llms-full.txt](/llms-full.txt) holds all of them in one file. - Every page has a Markdown twin: add `.md` to its address (`/docs/cli.md`), or ask for it with `Accept: text/markdown`. ```bash curl -H "Accept: text/markdown" https://quick-cli.com/docs/sites ``` --- # Custom domains ```bash quick domains add launch www.example.com # prints the DNS records to create quick domains launch # active, or still waiting for DNS quick domains rm launch www.example.com ``` At the domain's DNS provider, whichever it is, create: ``` www.example.com CNAME launch.quick.acme.com ``` Keep it DNS only, without a CDN proxy in front. An apex domain such as `example.com` can't have a CNAME: use your provider's ALIAS or CNAME flattening, or the same A record as the quick server, and add a TXT record with the site name so quick knows which site the domain is for: ``` _quick.example.com TXT launch ``` - The domain stays "waiting for DNS" until the record resolves through public resolvers (1.1.1.1, 8.8.8.8), then turns active on its own. - The HTTPS certificate is issued on the first visit. - A domain belongs to one site. It moves to another site only once its DNS points there (for an apex domain: once the TXT record names the other site). - Search engines can index a site on its custom domain: the `noindex` quick puts on `.` is not added there. Use `_headers` if you want it anyway. - Public and access-code sites are served on the custom domain. Sites behind company sign-in redirect visitors to `.`, because a sign-in does not carry across domains. Custom domains have to be switched on by whoever runs the server: see [Self-hosting](/docs/self-hosting#custom-domains). --- # Self-hosting A quick server is two containers behind your reverse proxy: - **quick-server** (`ghcr.io/zupolgec/quick-server`, built for amd64 and arm64 on every release) serves the sites, the API, the dashboard and the install scripts. - **oauth2-proxy** handles the company sign-in for browsers. The repository's [`docker-compose.yaml`](https://github.com/zupolgec/quick/blob/main/docker-compose.yaml) runs both. It is written for Coolify with caddy-docker-proxy labels, and reads as a reference for any other setup. ## How requests flow ``` browser ─https─> your proxy (TLS for the domain and *.domain) ─> quick-server:8080 quick.acme.com API, dashboard, /oauth2/* (to oauth2-proxy), /install.sh .quick.acme.com the site's access check, then its files CLI ─ sign-in with PKCE on a loopback port ─> ID token ─> quick.acme.com/api/* ``` The base domain is the control plane; every subdomain is a site. The proxy only forwards to quick-server, and quick-server decides everything by host name. ## DNS and TLS Point both the base domain and its wildcard at the server: ``` quick.acme.com A 203.0.113.10 *.quick.acme.com A 203.0.113.10 ``` The proxy needs a wildcard certificate for `*.quick.acme.com`, which means a DNS-01 challenge with your DNS provider. With that in place new sites need no DNS or certificate work. ## Identity provider **Google Workspace** (the default) needs two OAuth clients: - a **Web application** client for oauth2-proxy, with redirect URI `https://quick.acme.com/oauth2/callback`; - a client for the CLI with redirect URI `http://127.0.0.1:8765/callback`. Either a **Desktop app** client (set only its ID, the CLI uses PKCE without a secret) or the same Web client again (set ID and secret: the secret is handed to the CLI by the server, which is acceptable for a PKCE loopback client). Only accounts of the Workspace domains in `QUICK_ALLOWED_DOMAINS` get in: a personal Google account that uses a company address does not. Each sign-in asks which account to use. **Any OIDC provider** (Zitadel, Keycloak, Authentik and others): set `QUICK_OIDC_ISSUER=https://idp.acme.com`, a Web client for oauth2-proxy, and a native PKCE client for the CLI with the same loopback redirect. Tokens are verified locally against the issuer; the domain checked is the part of the email after `@`, and only if the email is verified. ## Configuration | Variable | Meaning | |---|---| | `QUICK_BASE_DOMAIN` | The base domain, e.g. `quick.acme.com` | | `QUICK_ALLOWED_DOMAINS` | Email domains allowed in: `acme.com`, `acme.com,acme.io`, or `*` for any account | | `QUICK_CLI_CLIENT_ID`, `QUICK_CLI_CLIENT_SECRET` | The CLI's OAuth client (secret only when reusing a Web client) | | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | oauth2-proxy's Web client (also for OIDC providers) | | `COOKIE_SECRET` | oauth2-proxy cookie secret: `openssl rand -base64 32` | | `QUICK_META_SECRET` | Signs access codes and site cookies: `openssl rand -base64 48` | | `QUICK_OIDC_ISSUER` | The issuer URL, for providers other than Google | | `QUICK_OWNERSHIP` | `free` (default), `shared` or `owned`; see [Access and safety](/docs/access) | | `QUICK_ADMINS` | Administrator emails, comma-separated: they manage every site, raise a single site's size limit and set the server's defaults on the dashboard's Settings page | | `QUICK_SMTP_URL`, `QUICK_MAIL_FROM` | Email sending, e.g. `smtp://user:pass@host:587` (or `smtps://` on 465) and `quick `. Lets chosen-people sites include people outside the company, who sign in with an email link | | `QUICK_SITE_LIMIT` | Size limit of every site, per version (default `100MB`) | | `QUICK_SITE_LIMIT_MAX` | Highest limit an administrator can give a site (default `2GB`) | | `QUICK_MIN_FREE` | On local disk, refuse deploys that would leave less free space than this (default `5GB`) | | `QUICK_STORAGE` | `local` (default) or `s3` | | `QUICK_S3_ENDPOINT`, `_REGION`, `_BUCKET`, `_ACCESS_KEY`, `_SECRET_KEY`, `_PREFIX`, `_USE_SSL` | S3-compatible storage | | `QUICK_HOST_DIR` | Host folder for the data, to run several instances on one host | | `QUICK_CHUNK_DIR` | Where uploaded chunks wait to be published (default: the system temp folder; cleared on publish or after 24 hours) | The server fails closed: outside local development it refuses to start without `QUICK_META_SECRET`, `QUICK_ALLOWED_DOMAINS` and the CLI client ID. To admit any account you write `*`, explicitly. ## Storage **Local disk** keeps site files and settings in two mounted folders. Files identical in the live and the previous version are stored once (hardlinks), so the rollback copy costs only what changed. Next to the settings the server keeps a map of the chunks of the live and the previous version, so incremental deploys never re-read the site; if the map is missing it is rebuilt from the files. **S3-compatible storage** (Hetzner, MinIO, R2, AWS and others) makes the container stateless. Each deploy writes to `versions///` and then switches one pointer, `live/.json`, which names the live and the previous version; rollback swaps them. Old versions are removed by later deploys after a 15-minute grace period, so requests in flight and other instances can finish reading them. Other instances see a new pointer within a few seconds. The bucket needs no versioning and no conditional writes. A lifecycle rule that removes incomplete multipart uploads is a good idea. One known limit: without conditional writes, a pointer write delayed by the network could land after a later deploy and bring the site back to that version. It is very unlikely; deploying again fixes it. ## Custom domains Custom domains need on-demand TLS on the proxy and a plain HTTP catch-all, on exactly one quick instance per proxy. In the reference compose they are switched on with `QUICK_CD_PREFIX=caddy`: the labels are named `${QUICK_CD_PREFIX:-quick_off}_1` and `_2`, so without the variable the proxy ignores them. The proxy must ask that instance before issuing a certificate (`on_demand_tls.ask`) and must have no catch-all of its own: quick answers 503 over HTTP to unknown domains. ## Upgrading Release tags build the CLI binaries and the server image. Pull `ghcr.io/zupolgec/quick-server:latest` (or a version tag) and recreate the container. Users update the CLI with `quick upgrade`. ## Local development ```bash QUICK_DEV_NOAUTH=1 QUICK_BASE_DOMAIN=quick.localhost \ QUICK_SITES_DIR=./sites QUICK_META_DIR=./meta QUICK_META_SECRET=dev \ go run ./cmd/quick-server ``` `QUICK_DEV_NOAUTH=1` skips sign-in checks and is for local development only.