Keep Next.js .next/cache when restoring a build on a persistent volume
If your Next.js container restores a pre-built .next directory onto a persistent volume at start-up, don't delete the whole directory first. .next/cache holds the data cache and the optimised images, and wiping it on every new build means each deploy starts cold and refetches from your backend. Delete everything in .next except cache, then copy the new build over the top.
Why is .next copied at start-up at all?
Some hosting setups mount a persistent volume over the application directory so that the running Next.js server can write to disk: incremental static regeneration output, cached fetch responses and optimised images all land under .next. The catch is that a volume mounted over a directory hides whatever the image had there. The build you produced in the Docker image is no longer visible at the path Next.js reads from.
The usual workaround is a two-step arrangement. During the image build you run next build and copy the result to a second location, for example .next_backup. When the container starts, a shell script compares the BUILD_ID file in the volume with the one in the backup. If they differ, or the volume is empty, the script restores the backup into .next before starting the server. If they match, as on a pod reschedule or a redeploy of the same commit, it leaves the volume alone.
What does rm -rf .next/* throw away?
The natural way to write the restore step is "empty the directory, then copy the build in":
rm -rf /app/.next/*
cp -r /app/.next_backup/* /app/.next/
That removes .next/cache along with the stale build output. According to the Next.js documentation, that folder contains the build cache and, at runtime, the cached fetch responses and the optimised images. None of those are tied to a build in the way compiled chunks are. A cached image variant is keyed by its source, width and quality, and a cached fetch response is keyed by its request. Deleting them on every code change means the first visitors after a deploy pay for the backend round trips and image resizing again. I haven't measured how large that cost is for any given site, so judge it against your own traffic, but it is work the server had already done and then discarded for no reason.
How do I restore the build and keep the cache?
Delete only the top-level entries other than cache:
find /app/.next -mindepth 1 -maxdepth 1 ! -name cache -exec rm -rf {} +
cp -r /app/.next_backup/* /app/.next/
-mindepth 1 stops find from matching the directory itself, -maxdepth 1 keeps it from descending into cache to check names, and ! -name cache skips the one entry you want to keep. The -exec ... {} + form batches the deletions into as few rm calls as possible.
There is a side benefit. The shell glob .next/* doesn't match names that start with a dot, so the original command also leaves hidden files behind. The find version removes them. If you rely on a hidden file surviving, you will notice, so it is worth a look at what is in your build directory.
What about the cache folder inside the backup?
next build writes its own cache directory, and the backup is a copy of the whole build, so it contains one too. cp -r merges directories: files with the same name are overwritten, and files that exist only in the volume are kept. That is usually what you want, but check it against your own layout. If you would rather the runtime cache win, exclude cache from the backup in the Dockerfile, or copy with cp -rn so existing files are not replaced. Whichever you choose, do it deliberately rather than by accident.
When should I clear the cache anyway?
Keeping the cache assumes that an old entry is still a valid answer to the same request. That holds for optimised images and for fetch responses that your revalidation settings already expire. It does not hold if a deploy changes the shape of the data that your backend returns for the same URL, or changes how the response is used. In that case a cached response from the old shape can be served to new code until it expires. If your releases do that, add an explicit step: set a flag, bump a cache version in the request, or clear the folder for that one deploy. Keeping the cache as the default is reasonable; being unable to clear it is the real hazard.
Test the behaviour rather than trusting the script. Start a container with a populated volume and an older BUILD_ID, run the start-up script, and check that the build files were replaced while a marker file you placed in cache is still there. Then run it again with a matching BUILD_ID and confirm nothing was touched.
Key takeaways
- When a persistent volume hides the image's
.nextdirectory, a start-up script restores the build from a backup copy when theBUILD_IDvalues differ. rm -rf .next/*in that script also deletes.next/cache, so every deploy loses the cached data and optimised images.find .next -mindepth 1 -maxdepth 1 ! -name cache -exec rm -rf {} +removes the old build and leaves the cache in place.cp -rmerges into an existing directory, so check whether the build-time cache in your backup should overwrite the runtime one.- Keep the cache by default, but make sure you have a way to clear it for a release that changes the shape of the data your pages fetch.