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.
Publish from the dashboard
Section titled “Publish from the dashboard”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.
What the dashboard route does not do
Section titled “What the dashboard route does not do”- 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
--previewwith 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.
Publish from the command line
Section titled “Publish from the command line”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.
Prerequisites for the CLI
Section titled “Prerequisites for the CLI”- 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 setDEPLOYBASE_API_KEYto adbk_API key
Deploy a directory to production
Section titled “Deploy a directory to production”Point the CLI at the directory your build produced:
deploybase deploy ./distUploading 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:
URL=$(deploybase deploy ./dist)The deployment is live when the command returns.
Deploy a zip
Section titled “Deploy a zip”deploybase deploy ./site.zipThe 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:
unzip -q site.zip -d ./unpackeddeploybase deploy ./unpacked/distDeploy to a preview
Section titled “Deploy to a preview”deploybase deploy ./dist --preview feat-navThe 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.
Label a deployment
Section titled “Label a deployment”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.
Skip an upload when nothing changed
Section titled “Skip an upload when nothing changed”deploybase deploy ./dist --skip-unchangedThe 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.
Use it from CI
Section titled “Use it from CI”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.
GitHub Actions
Section titled “GitHub Actions”- 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" --quietWoodpecker
Section titled “Woodpecker”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.
Exclude files with .deploybaseignore
Section titled “Exclude files with .deploybaseignore”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.logA 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.
What is always excluded
Section titled “What is always excluded”.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.
Limits
Section titled “Limits”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.
Troubleshooting
Section titled “Troubleshooting”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.
Questions this raises
Section titled “Questions this raises”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.