We have dedicated DEV, TEST, STAGE, and PRODUCTION servers.
We also have a dedicated build machine which runs Cruise Control.
Cruise Control is configured for a Continuous Integration build, which runs after code is checked in. It is also configured for separate Development, QA, Stage, and Production tasks.
To deploy to development, the code is first retrieved from SVN and built, then the "Precompiled Web" folder is copied to the development web site, and the web service project is copied to the development application server. Cruise Control is also configured to "tag" the source code before the build starts so we can reproduce the build at a later time, or branch from the tag if we need to do a hot fix.
To deploy to QA, the files are copied from the development machines to the QA machines.
Likewise, to deploy to Stage the files are copied from the QA machines to the Stage machines.
Finally, to deploy to production, the files are again copied from the Stage machines to the Production machines.
To configure each environment, we have a custom tool which is part of each environment's Cruise Control task that modifies connection strings, "debug=true|false", "customErrors=Off|RemoteOnly", and other environment-specific settings.
So each environment can be deployed with a button push from the Cruise Control dashboard.
One caveat is that we currently have the production database password configured in the Cruise Control config file...it would be nice move it elsewhere!
Lastly, let me add that even though our production machines are in a dedicated hosting facility, the servers are accessible from our Cruise Control machine, which makes it very easy to do a production deployment. The only manual step is to encrypt the web.config files and remove the "AppOffline.html" file that Cruise Control puts up.
Let me know if this helps, or if you have any questions.
Thanks!