📝 Originally published (in Japanese) at forge.workstyle.tech.
TL;DR The setup is an Astro static site deployed to GitHub Pages with GitHub Actions, with DNS managed by Cloudflare. After two blog posts were rewritten and pushed on 2026-09-28, deployment failed because Actions storage had reached its limit. The blog had one remaining artifact of about 20MB, while a larger artifact in another repository in the same account was the main cause.
1. Check the storage limit on the billing page
First, check your Actions storage, Packages storage, and Actions minutes on GitHub’s billing page. Storage and execution time are separate categories, so check both.
Here’s what my account showed:
Actions storage 2GB / 2GB
Packages storage 0GB
Actions minutes 512 / 3,000 minutes
Actions storage had reached its limit, while I still had minutes available. The page said, “Your quota resets in 3 days.”
A common pitfall is to look only at the artifacts in the repository where the workflow failed and assume that they explain the problem. When I first checked, the blog repository had just one artifact, about 20 MB in size. Even so, the storage limit had been reached.
During deployment, I got this error. Retrying twice produced the same result:
Failed to CreateArtifact: Artifact storage quota has been hit. Unable to upload any new artifacts. Usage is recalculated every 6-12 hours.
When you see this message, check your account-wide Actions storage, not just the artifact list in the repository where the workflow failed.
2. Compare Monthly Billing API Data Against Each Repository
Next, check the target month’s Actions storage through the billing API. The number to look at isn’t just the total size of artifacts currently stored. It’s the total in GB-hours, which combines storage size and retention time.
In my case, Actions storage for September came to 1,488 GB-hours. The charge was attributed to the blog repository, but that didn’t match how the blog used storage.
The blog had 52 deployments that month. Each artifact was about 20 MB and was retained for one day. Based on that, its estimated monthly usage was around 25 GB-hours—a large gap from 1,488 GB-hours.
When you find a gap like this, check the other repositories in the same account too. In my case, the cause was Windows builds for a desktop app. Each build produced an artifact of about 340 MB, retained for seven days, which came to around 57 GB-hours per build. The Windows builds ran for 237 minutes in September.
I also checked the monthly cost breakdown. The account total was $3.57, of which $2.87—about 80%—came from that repository.
It’s easy to assume that the repository shown in the billing breakdown is the one that actually created the artifacts. Even if the charge is attributed to the blog, compare it with the GB-hours estimated from the blog’s deployment count and artifact sizes, then investigate artifacts from other repositories.
3. Shorten the Retention Period for Large Artifacts
In the Windows build that caused the issue, artifacts were retained for 7 days. The first step is to shorten the retention period to 1 day. In my case, this reduced usage per run from about 57 GB-hours to about 8 GB-hours.
If your Actions workflow stores artifacts, review retention-days in the settings for the relevant upload step. For example, if it’s set to 7, change it to 1:
# Example configuration
with:
retention-days: 1
A common pitfall is shortening the retention period for files that need to remain available as Actions artifacts. In my case, I decided to attach the Windows installer to a Release instead. Releases don’t count toward Actions storage, so there was no need to keep the installer as an Actions artifact for 7 days.
After making the change, confirm that the new retention period is configured for artifacts in the target workflow. Monthly usage figures may take a while to decrease. The error message in this case said that usage is recalculated every 6–12 hours.
4. Configure Static Asset Delivery with Workers
Even after reviewing the retention period, my blog deployment still depended on uploading artifacts through Actions. I decided to move the blog to Cloudflare.
Before migrating, I checked the size of the site. It was 26 MB across 194 files, including 96 images. The largest image was 0.66 MB. That fit within the free-tier limits of 20,000 files per deployment and 25 MiB per file.
I first ran wrangler pages project create, but it delegated to Workers instead of Pages and stopped with this error:
Missing entry-point to Worker script or to assets directory
Today, use static asset delivery with Workers instead of Pages. Set Astro’s build output directory, dist, as the directory to serve. My wrangler.jsonc configuration looks like this:
{
"name": "forge-blog",
"compatibility_date": "2026-09-28",
"assets": {
"directory": "./dist",
"html_handling": "auto-trailing-slash"
}
}
html_handling controls how trailing slashes in URLs are handled. Once the configuration file is in place, build and deploy:
npx astro build
npx wrangler deploy
One common snag is configuring a 404 page that doesn’t exist. I initially tried setting not_found_handling to 404-page, but dist didn’t contain a 404.html, so I removed that setting. Check that your configuration matches the files the build actually generates.
5. Switching DNS and the Publishing Script
Once the site is deployed to Workers, point the domain to it. Cloudflare already managed DNS for my blog.
I deleted the forge CNAME record (which pointed to github.io) and added a custom domain to the Worker. There was a brief outage of a few minutes during the switch. Plan for when you’ll make the DNS change and allow for a short interruption in service.
A common pitfall is changing DNS but leaving the publishing process as it was. I updated scripts/publish.sh to commit and push the articles, then build Astro locally and deploy with Wrangler. The relevant part of the publishing script looks like this:
npx astro build
npx wrangler deploy
I kept the GitHub Pages workflow as a rollback option. It no longer runs on push; it can only be triggered manually. Here’s the configuration for that workflow:
on:
workflow_dispatch:
workflow_dispatch enables manual runs. Verify that the normal publishing path and the rollback workflow each run under the intended conditions.
6. Verify URLs and Responses After Launch
After switching over, don’t stop at checking whether the homepage loads. Check the homepage, an article page, the RSS feed, and robots.txt. After my migration, all of them returned HTTP 200. The server response header was cloudflare, and the Google Analytics tag was still present.
Replace these example URLs with the ones for your actual site.
curl -I https://forge.workstyle.tech/
curl -I https://forge.workstyle.tech/article-slug/
curl -I https://forge.workstyle.tech/rss.xml
curl -I https://forge.workstyle.tech/robots.txt
Check more than the HTTP status: confirm that the response headers include server: cloudflare and that the pages that need it contain the analytics tag. Use the actual path to an article on your site.
A common pitfall is to consider the migration complete after checking only the homepage. Verify individually that the RSS feed and robots.txt are still served as before, and that article pages return 200 as well.
Key takeaways
- Check Actions storage separately from execution time on the billing page.
- Compare monthly GB-hours from the billing API with artifact size, retention period, and run count across repositories.
- Shorten retention for large artifacts and use a separate storage location for files that need to remain available longer.
- After moving the blog to Cloudflare Workers, configure the regular publish path and a rollback path, then check article pages, RSS, and robots.txt.
Top comments (0)