Problem
When deploying an ASP.NET Core application to IIS on Windows Server 2022, I sporadically encounted a problem where one or more .dll files were locked. This prevented the deployment from working.
The error: "Web Deploy cannot modify the file 'MySite.dll' on the destination because it is locked by an external process. In order to allow the publish operation to succeed, you may need to either restart your application to release the lock, or use the AppOffline rule handler for .Net applications on your next publish attempt."
On Azure, this issue is addressed and files are renamed with the flag: MSDEPLOY_RENAME_LOCKED_FILES=1 as detailed here. On a VPS or hardware instance of IIS however, there is no such option.
It's imperative to have a reliable release process. Logging onto the server and running iisreset is a last resort. It shouldn't be part of a release process.
Unfortunately, the file locking problem has plagued .NET Core since its inception, as seen here.
The error message given by MSDeploy when this problem is encountered suggests using an app_offline.htm parameter, which creates a file all traffic goes to, or restarting the application. Neither of these methods work. It can be deceiving to believe they do since the error shows up sporadically.
Attempts At A Fix
None of these suggestions work:
- Deleting the web.config file with MsDeploy, then deploying (Note: not a good practice for security reasons)
- Stopping the application pool with appcmd, then deploying
- Stopping the site with appcmd, then deploying
- Recyling the application pool with appcmd, then deploying
- Placing an app_offline.htm file in root of the application, then deploying
No matter what, there isn't a consistent way to deploy the application without occasionally seeing a message about a locked .dll file.
On the first deployment there isn't a lock issue but in practice it is only the updates which matter. Applications are constantly being worked on, unless there is some kind of load balancer and fresh directory to point to, this issue must be addressed.
I tried every AppCmd.exe command that should have addressed the issue. In using MsDeploy to run commands, I ran into a permission error of: "A required privilege is not held by the client." To address the permissions, I ended up going onto the web server and running commands on cmd outlined here. In doing so, I had to change permissions and restart the web deploy service.
Nothing I was doing with appcmd (stop or recycle, then deploy, then start) was solving the problem. Stopping the application pool or the site doesn't release file locks, at least not in a predictable or timely manner.
Solution
The only solution I could find for this issue, without substantial changes by making and moving directories, had to do with making a shadow copy of the files through changes to the web.config outlined here.
After the MSDeploy command runs with this change, the site is still not ready. It takes some time, about 10+/- seconds, in the background. I end up polling the root of the application until it comes online with a success status. This is still better than having the deployment completely fail.
MSDeploy, located at: "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" (downloadable here) is what I use to release the application.
This is what the web.config ended up needing to look like (changing the name to the .exe of the project):
Here is the PowerShell to deploy a .zip file package and check the website with retry logic:
During Transition
Using the settings I have provided, while the site is coming online, a "Site Under Construction" message is displayed in the header. This is better than an error page from a security and user experience perspective.
Summary
By modifying the web.config, deploying with MSDeploy and using PowerShell to check the result, it is now possible to deploy any .NET Core 7.0 application consistently without file lock errors. It's important to poll the site after the deployment to ensure that it eventually transitions out of having a 503 status code to a 200 status code. If it doesn't, then the deployment has an actual problem beyond file locking.
If you are interested in the GitHub discussion on this topic (shadow copying for ASP.NET Core), go here.