Rebuild cached static website Docker layers in Easypanel
Products like Easypanel, CapRover, and Coolify are bringing the PaaS experiences like Netlify and Vercel to self-hosting. I recently gave Easypanel a try on a DigitalOcean VPS, to replicate the effortless deployment flow I use when integrating Github, Vercel, and Sanity (the headless CMS I use for this website).
Out of the box it supports importing Github repos and redeploying on new commits to whichever branch you prefer. These even use Heroku’s Buildpacks to automatically build and deploy common application setups, with zero configuration.
Easypanel even supports custom webhooks that Sanity can call whenever content in my CMS is updated.
Setting up my site in Easypanel
All of this worked seamlessly, and I was quickly serving up my site from a shiny new private server. The Buildpacks produce fairly large docker image sizes, quickly consuming disc space, so I wrote a small custom image to handle building and serving up a static Astro website.
I encountered one snag.
The webhook only calls the “Deploy” function of a service in Easypanel. This checks the Github repo to see if new code is present, and if not, no new build is generated.
Since all my content is hosted externally to the code repo, Easypanel won’t see any pending changes and skips recompiling the Docker image.
I moved on, then, to always log in to the Easypanel dashboard and click the “Force Rebuild” button.
Unfortunately, this didn’t work as I assumed it would.
Now the Docker build process is being re-run, but Docker itself also aggressively caches each layer of the build process to ensure it doesn’t need to repeat work.
With the Astro site being compiled during the build process of the Docker container, and still, no code changes being present, the Astro recompilation is skipped. Again.
I looked up possible solutions online, many included adding timestamp arguments to the executed Docker commands, but Easypanel doesn’t expose this kind of dynamic configuration.
Adding a Dockerfile cache bust layer
Instead, I followed a tip mentioned here, to add a cache-busting environment config to the service.
Immediately preceding the build command in the Dockerfile, I added this CACHEBUST
argument, which imports environment variables of the same name during the Docker build process:
# Installing dependencies and setting up the docker image
# Args/Environment variables
ARG CACHEBUST=0
# Build the site
RUN yarn build
Then, in the Environment tab under my service in Easypanel, I added the same environment parameter:
CACHEBUST=0
Now, anytime I wanted to force Docker to recompile my Astro site with new content from Sanity I would navigate to the Environment tab on my Easypanel service, increment CACHEBUST
by one, save, and click “Force Rebuild”.
This approach can be used for any static website that is compiled during the Docker build process, including Astro, Next.js, Gatsby, and more.
Serving static websites in Docker
In building out the Dockerfile, I discovered Static Web Server. Using the tiny joseluisq/static-web-server
Docker image was a fast, zero-config way to serve the build output from Astro.
Switching back to Vercel
Over time this process became tedious and I switched back to Vercel. I enjoyed self-hosting and getting my hands dirty experimenting with Dockerfiles, but I knew long-term it would be too much maintenance to keep up with. If and when Easypanel or a similar self-hosted PaaS solves this issue, I would love to re-explore the product segment.
I did also experiment with CapRover and Coolify, but both were missing the external webhook I needed for Sanity to trigger rebuilds of my website when content has been added or updated.