Skip to content

Deploy a pre-built site

You can publish a site you have already built without a repository and without a remote build. deploybase stores the files and publishes the deployment, so what you see locally is exactly what goes live.

There are two routes to the same result:

  • The dashboard. Drag the folder onto the page. Nothing to install.
  • The CLI. Scriptable, runs in CI, and handles zips, previews and repeat publishes.

If the site does live in a repository deploybase can reach, the Git flow is less work: push, and the build runs for you.

Drag the folder onto the page. The files you drop are the files that go live.

For a new project, open Projects, choose New project, then Upload a folder. Name the project, choose Create project, and drop your folder onto the drop zone that appears.

For a project that has no repository, open it. While the project has no deployments, its Deployments tab carries the same drop zone.

Pick the folder your home page sits in, because its contents become the root of your site. Dropping a folder that contains dist/index.html publishes the site one level down, reachable at /dist/ rather than at /. Drop the dist folder itself instead. If dragging is awkward, choose browse your computer and use the folder picker.

The upload goes to production, and the deployment page opens once the site is live. You need the member role or higher, the same as any other deploy.

  • Zips. The drop zone takes a folder. Use the CLI for a zip, or unzip it and drop the folder.
  • Projects that already have deployments. The drop zone appears only while a project has none. Publish again with the CLI.
  • Previews. Dashboard uploads go to production. Use --preview with the CLI for a preview.
  • .deploybaseignore. That file is read by the CLI only. The dashboard excludes the set under what is always excluded and nothing else, and the ignore file itself uploads like any other file.

deploybase deploy <dir> uploads a site you have already built. Reach for it when your CI already produces the artefact, when your toolchain is one deploybase does not run, or when the source is not somewhere it can clone from.

  • A project in deploybase. Create one from the dashboard, or link an existing one with deploybase link
  • The CLI installed: go install codeberg.org/deploybase/deploybase-cli@latest, or a binary from the releases page. See the CLI reference for details
  • Authentication: run deploybase login, or set DEPLOYBASE_API_KEY to a dbk_ API key

Point the CLI at the directory your build produced:

Terminal window
deploybase deploy ./dist

Uploading to production is the default when you pass a directory or a zip. deploybase deploy --prod ./dist is the same thing, written out.

Progress goes to standard error, and the deployment URL alone goes to standard output, so this works:

Terminal window
URL=$(deploybase deploy ./dist)

The deployment is live when the command returns.

Terminal window
deploybase deploy ./site.zip

The CLI expands the zip into a temporary directory, checks every entry, and uploads the result. Entries that are symlinks, absolute paths, or that point outside the archive are rejected before anything is sent.

If every file in the zip sits inside a single top-level folder, for example dist/index.html and dist/about/index.html, the CLI warns you. It does not strip that folder: your site would be published one level down, with nothing at the root. Unzip it and deploy that folder instead:

Terminal window
unzip -q site.zip -d ./unpacked
deploybase deploy ./unpacked/dist
Terminal window
deploybase deploy ./dist --preview feat-nav

The name becomes the preview’s label, and the site is served at {name}-{preview-slug}.previews.deploybase.eu. Previews are not indexed by search engines.

--prod and --preview cannot be combined. When you deploy a directory or a zip, use --preview <name> rather than --branch: there is no Git branch involved, and the name is only the label the preview hostname is built from.

Terminal window
deploybase deploy ./dist --label "release 4.2 from CI"

The label is stored with the deployment in place of a commit message. It is free text, and it does not affect routing.

Terminal window
deploybase deploy ./dist --skip-unchanged

The CLI hashes your files, builds a content hash for the whole set, and compares it with the deployment currently serving that target. When the two match, it uploads nothing, prints the existing URL and exits 0. In --json mode the result carries skipped_unchanged: true.

The comparison target depends on the mode:

  • Production: the project’s current live deployment
  • Preview: the most recent deployed deployment for that preview name. This is best-effort, because the CLI looks at the most recent 100 deployed deployments of the project. If the match is older than that, the upload runs

This is useful in a pipeline that runs on every commit but only sometimes changes the site.

Create an API key and store it as a secret. The CLI picks it up from DEPLOYBASE_API_KEY, so no login step is needed.

- name: Build
run: npm ci && npm run build
- name: Deploy to deploybase
env:
DEPLOYBASE_API_KEY: ${{ secrets.DEPLOYBASE_API_KEY }}
run: |
go install codeberg.org/deploybase/deploybase-cli@latest
deploybase deploy ./dist --project "$PROJECT_ID" --quiet
steps:
deploy:
image: golang:1.25
environment:
DEPLOYBASE_API_KEY:
from_secret: deploybase_api_key
commands:
- go install codeberg.org/deploybase/deploybase-cli@latest
- deploybase deploy ./dist --project "$PROJECT_ID" --json

--quiet prints the URL and nothing else. --json prints a single object with the deployment id, URL and upload counters, which is easier to parse in a later step. Pass --project <id> when the working directory is not linked to a project.

Put a .deploybaseignore file in the directory you deploy. It is read from that directory only, and the file itself is never uploaded.

The syntax is a subset of .gitignore:

  • # starts a comment, blank lines are ignored
  • * matches within one path segment, ? matches one character, ** matches across segments
  • A leading / anchors the pattern to the root of the upload
  • A trailing / matches directories only
  • ! negates a pattern
  • The last matching rule wins

Character classes such as [abc] are not supported.

# build metadata we do not want on the CDN
*.map
/reports/
**/*.log
# but keep this one
!/changelog.log

A rule that excludes a directory also excludes everything inside it, and a later ! rule cannot bring a file back out of an excluded directory. That matches how Git treats .gitignore.

.git, node_modules, .DS_Store, __MACOSX, .env and any .env.* are never uploaded, by either route and whatever your ignore file says. The dashboard reports how many files it left out once the upload starts.

Symlinks are not followed. The CLI stops with an error naming the symlink rather than skipping it quietly, so you always know what was left out.

A Git build withholds a slightly different set, because its risks are different. See what is never published.

Upload limits, from file size to session lifetime, are in the CLI reference and apply to both routes.

Both check before uploading anything, so an oversized site fails immediately and costs you nothing: the CLI checks file size and file count locally, and the dashboard also checks the total against your plan allowance before it starts.

409 hash mismatch. The content of a file changed between the moment the CLI hashed it and the moment it was sent, usually because a build or a watcher was still running. Let the build finish and run the deploy again.

410 session expired. An upload session is open for two hours. A very large upload on a slow link can run out of time. Run the deploy again. Each run opens a fresh session, so start it when the link is quiet.

402 plan limit. The upload is larger than your plan allows in total. Remove what does not need to be on the CDN, for example source maps or original-resolution images, or upgrade the plan.

429 too many uploads. A team can start 60 uploads per hour. Wait for the window to move on, or deploy less often. A pipeline that deploys on every commit of a busy repository is the usual cause.

Manifest rejected. The API refused a path. Paths must be relative, must use forward slashes, and cannot contain .. or empty segments. The error names the offending path and the reason.

Can I use SFTP, rsync or WebDAV? No. The upload API and this CLI are the supported way to publish files, and there is no file-transfer endpoint to point another tool at.

Can I build my own client? Yes. The upload endpoints are public and documented in the API reference under uploads: begin an upload with a manifest of paths, hashes and sizes, send the files the response asks for, then finalize. The CLI is an ordinary client of that API.

An uploaded deployment is an immutable snapshot like any other, so it promotes and rolls back exactly like one built from Git. The two routes can be mixed on the same project.