Deploying

baudelaire build writes a plain folder of files to public/. Hand that folder to any host, or let baudelaire deploy push it for you.

baudelaire deploy --dry-run   # print the plan, upload nothing
baudelaire deploy             # confirm, then upload
baudelaire deploy --yes       # no prompt, for CI

What a deploy does

It builds the site first, so a run always reflects the current sources. Then it reconciles the destination with public/: uploads what is new or changed, skips what already matches, and deletes what the build no longer produces.

A dry run stops after printing that plan, one line per destination: how many files would upload, how many would be deleted, how many are already current.

Change detection compares content digests, and both backends read those digests from the remote itself. There is no local state to keep or lose, so re-deploying an unchanged site uploads nothing.

Pick a destination

Two destinations are built in. Both are configured under deploy, and the presence of the block turns one on:

deploy {
  s3 {
    bucket "my-site"
    endpoint "https://ACCOUNT.r2.cloudflarestorage.com"
  }
}
Destination Block Page
S3-compatible bucket deploy { s3 } S3-compatible storage
SSH host, over SFTP deploy { ssh } SSH & SFTP

Declare both and each runs in turn, confirmed separately.

NOTE

With no deploy block at all, baudelaire deploy errors instead of exiting 0 having published nothing.

Every other host takes the folder rather than a push:

Host Page
GitHub Pages GitHub Pages
GitLab Pages GitLab Pages
Forgejo, Gitea, Codeberg Forgejo & Gitea
Netlify, Vercel, Cloudflare Pages, nginx Static hosts

Confirmation

Each destination asks before it writes anything. -y / --yes grants it up front.

WARN

Off a terminal with no --yes, the deploy fails rather than skipping. A publish nobody authorized must not report success.

Credentials

Credentials never live in config.kdl. Each backend reads its own environment variables at deploy time, and --secret supplies the one secret it prompts for (the S3 secret access key, or the SSH password or key passphrase):

echo "$AWS_SECRET_ACCESS_KEY" | baudelaire deploy --secret -

--secret - reads one line from stdin, so the value never appears in the process arguments. A literal --secret <value> wins over everything, including the environment, but it lands in your shell history. Resolution order per backend is on the S3 and SSH pages.

Preview builds

Point the canonical URL at the preview host, so feeds, the sitemap and social cards emit the right absolute links:

baudelaire build --base-url "https://preview.example.com"

The url path component is honored too, so a site served under a subdirectory (url "https://example.com/docs") prefixes every root-absolute link with /docs and leaves the on-disk layout alone. A named profile does the same from config: baudelaire --profile preview deploy.

Flags

Flag Does
--dry-run Report the plan and write nothing.
-y, --yes Skip the confirmation prompt.
--secret <value> The destination’s secret. - reads it from stdin.
--base-url <url> Override the canonical URL for this run.
--out <dir> Override the output directory.
--no-cache Ignore the incremental cache and rebuild everything.

deploy takes the build flags too, since it builds. See the CLI reference.

Builds in CI

Builds are incremental, and the state lives in .baudelaire/. Cache that directory between runs and a rebuild only touches what changed. Every CI recipe on the following pages does it.

Announcing is not deploying

baudelaire announce uploads none of public/. It publishes the site’s metadata to an AT Protocol repo. Deploy the files with one of the methods here, then see Announcing.