Deploying Astro 6 to Cloudron Surfer
Plane of Infrastructure: Cloudron Surfer
This is the first post in the Plane of Infrastructure series, covering self-hosting, Cloudron, and the tools that run beneath the surface of this blog.
Cloudron is a self-hosted platform for running web applications. It handles SSL certificates, backups, user management, and updates. Surfer is Cloudron’s built-in static file server. You point a domain at it, upload your files, and it serves them over HTTPS.
This blog runs on Surfer. Getting it there was not as simple as it should have been.
The surfer CLI Lies By Omission
The surfer CLI is the official tool for uploading files to a Surfer instance. The command looks straightforward:
surfer put dist/* /This command will appear to succeed. It will not throw an error. It will not warn you. But it silently skips two categories of files:
- Root-level HTML files (
index.html,404.html) - New hashed assets in
_astro/that do not already exist on the server
The result is a deploy that looks successful but leaves the site broken or stale. CSS and JavaScript files 404. The homepage serves the old version. You spend an hour checking your build output before realising the upload tool is the problem.
The Fix: curl and WebDAV
Surfer exposes a WebDAV endpoint at /_webdav/. Uploading via curl bypasses the CLI entirely and reliably uploads every file type.
# Upload a single filecurl -T file "https://wanderingmonster.dev/_webdav/path/to/file" \ -u "ignored:${SURFER_TOKEN}"
# Create a directory (MKCOL)curl -X MKCOL \ "https://wanderingmonster.dev/_webdav/_astro/" \ -u "ignored:${SURFER_TOKEN}"The username field is ignored by Surfer’s WebDAV. You can put anything there. The token is what matters.
The full deploy script walks the dist/ directory, creates all directories with MKCOL first, then uploads every file with PUT. The script is at scripts/deploy.sh in this repo.
Getting the Token
The Surfer WebDAV token is not your Cloudron account password. This is the mistake everyone makes the first time.
The token is a per-app API token found in the Surfer admin panel:
- Go to
https://your-site/_admin/ - Log in with your Cloudron credentials
- Find the token in the settings section
Set it as an environment variable:
export SURFER_TOKEN="your-token-here"pnpm run deployAccess Control
One more thing that will trip you up: the Surfer app must have access control set to Public in the Cloudron dashboard. If it is set to anything else, every page returns a 401 Unauthorized response.
This is configured in the Cloudron dashboard under the app settings, not in the Surfer admin panel. They are different interfaces for different things.
The Deploy Workflow
With the WebDAV script in place, the workflow is:
export SURFER_TOKEN="your-token"pnpm run deployWhich runs:
astro buildto generate the static site intodist/pagefind --site distto generate the search indexbash scripts/deploy.shto upload everything via WebDAV
After deploying, verify with a hard refresh (Ctrl+Shift+R) and check that /_astro/ CSS and JS files load without 404s.
Verifying a Deploy
A quick checklist after every deploy:
- Hard refresh the site in your browser
- Check that
/_astro/CSS and JS files load (open devtools network tab) - Check that root pages (
/,/blog,/rss.xml) render correctly - If CSS is missing, the hashed
_astro/files were not uploaded
If step 4 fails and you used the WebDAV script, check that SURFER_TOKEN is correct and that the app has public access control enabled.
Lessons Learned
Every lesson in this post was learned the hard way. The surfer put silent skip cost several hours of debugging. The token confusion cost another round. The access control 401 was the final boss.
Self-hosting is worth it for ownership and privacy. But the tooling has rough edges. Document them, script around them, and do not trust a CLI that succeeds silently.
The deploy script in this repo is the result. It works. Use it.