In November 2010, with SDK 1.3, Microsoft introduced the ability to deploy multiple web applications in a single Windows Azure web role. This is a great cost savings benefit since you don’t need a new role – essentially a virtual machine – for each web application you want to deploy.
Creating a web role that contains multiple web sites is pretty easy. Essentially, you need to add multiple <Site> elements to your web role’s ServiceDefinition.csdef file. Each <Site> element would include a physicalDirectory element that references the location of the web site to be included.
<Sites> <Site name="WebRole1" physicalDirectory="..\..\..\WebRole1"> <Bindings> <Binding name="Endpoint1" endpointName="Endpoint1" /> </Bindings> </Site> <Site name="WebApplication1" physicalDirectory="..\..\..\WebApplication1\"> <Bindings> <Binding name="Endpoint1" endpointName="Endpoint2" /> </Bindings> </Site> <Site name="WebApplication2" physicalDirectory="..\..\..\WebApplication2\"> <Bindings> <Binding name="Endpoint1" endpointName="Endpoint3" /> </Bindings> </Site> </Sites>
For additional detailed information on creating a web role with multiple web sites, I suggest following the guidance provided at these excellent resources:
- MSDN -Â http://msdn.microsoft.com/en-us/library/windowsazure/gg433110.aspx
- Wade Wegner’s blog post - http://www.wadewegner.com/2011/02/running-multiple-websites-in-a-windows-azure-web-role/
- ElastaCloud Blog post – http://blog.elastacloud.com/2011/01/11/azure-running-multiple-web-sites-in-a-single-webrole/
The above resources provide a great starting point. However, there is a once piece of what I think is important information that is missing. When Visual Studio and the Windows Azure SDK (via CSPACK) create the cloud deployment package (.cspkg), the content listed at the physicalDirectory location is simply copied into the deployment package. Meaning, any web applications there are not compiled as part of the process, no .config transformations take place, and any code-behind (.cs) and project (.csproj) files are also copied.
What’s going on? CSPack is the part of the Windows Azure SDK that is responsible for creating the deployment package file (.cspkg).  As CSPack is part of the core Windows Azure SDK, it doesn’t know about Visual Studio projects.  Since it doesn’t know about the Visual Studio projects located at the physicalDirectory location, it can’t do any of the normal Visual Studio build and publish tasks – thus just copying the files from the source physicalDirectory to the destination deployment package.
However, when packaging a single-site web role, CSPack doesn’t rely on the physicalDirectory attribute.  With a single-site web role, the packaging process is able to build, publish, and create the deployment package.
The Workaround
Ideally, each web site should be published prior to packaging in the .cspkg. Currently there is not a built-in way to do this.  Fortunately we can use MSBuild to automate the build and publish steps.
- Open the Windows Azure project’s project file (.ccproj) in an editor. Â Since the .ccproj is a MSBuild file, additional data points and build targets can be added here.
- Add the following towards the bottom of the .ccproj file.
<PropertyGroup>
<!-- Inject the publication of "secondary" sites into the Windows Azure build/project packaging process. -->
<CoreBuildDependsOn>
CleanSecondarySites;
PublishSecondarySites;
$(CoreBuildDependsOn)
</CoreBuildDependsOn>
<!-- This is the directory within the web application project directory to which the project will be "published" for later packaging by the Azure project. -->
<SecondarySitePublishDir>azure.publish\</SecondarySitePublishDir>
</PropertyGroup>
<!-- These SecondarySite items represent the collection of sites (other than the web application associated with the role) that need special packaging. -->
<ItemGroup>
<SecondarySite Include="..\WebApplication1\WebApplication1.csproj" />
<SecondarySite Include="..\WebApplication2\WebApplication2.csproj" />
</ItemGroup>
<Target Name="CleanSecondarySites">
<RemoveDir Directories="%(SecondarySite.RootDir)%(Directory)$(SecondarySitePublishDir)" />
</Target>
<Target Name="PublishSecondarySites" Condition="'$(PackageForComputeEmulator)' == 'true'
Or '$(IsExecutingPublishTarget)' == 'true' ">
<!--
Execute the Build (and more importantly the _WPPCopyWebApplication) target to "publish" each secondary web application project.
Note the setting of the WebProjectOutputDir property; this is where the project will be published to be later picked up by CSPack.
-->
<MSBuild Projects="%(SecondarySite.Identity)" Targets="Build;_WPPCopyWebApplication" Properties="Configuration=$(Configuration);Platform=$(Platform);WebProjectOutputDir=$(SecondarySitePublishDir)" />
Finally, for each secondary site defined in the .csdef, update the the physicalDirectory attribute to reference the publishing directory, azure.publish\.
<Site name="WebApplication1" physicalDirectory="..\..\..\WebApplication1\azure.publish"> <Bindings> <Binding name="Endpoint1" endpointName="Endpoint2" /> </Bindings> </Site>
How does this all work? Â By adding the CleanSecondarySites and PublishSecondarySites targets to the <CoreBuildDependsOn> element, that is forcing CleanSecondarySites and PublishSecondarySites to happen before any of the default targets included in <CoreBuildDependsOn> (defined in the Microsoft.WindowsAzure.targets file). Thus, the secondary sites are built and published locally before any of the default Windows Azure build targets execute. The IsExecutingPublishTarget condition is needed to ensure PublishSecondarySites happens only when packaging the Windows Azure project (either from Visual Studio or via the command line with MSBuild).
UPDATE (1/15/2013): I’ve added a PackageForComputeEmulator condition to the PublishSecondarySites target. This should ensure the target is also executed when debugging locally via the compute emulator.
I like this approach because it seems like a fairly clean approach and I don’t have to modify build events (keep reading) for each secondary web site I want to include. Â I can keep the above build configuration snippet handy and use it in future projects quickly.
Alternative Approach
The above approach relies on modifying the project file to help automate the build and publish of any secondary sites.  An alternative approach would be to leverage build events.  I first learned of this approach from “Joe” in the comments section of Wade Wegner’s blog post.
- Select your Windows Azure project, and select Project Dependencies. Â Mark the other web apps as project dependencies.

- Open the project properties for each web application (not the one used as the Web Role).
- For the Build Events, you’ll need to add a pre-build and post-build event.
Pre-Build
rmdir "$(ProjectDir)..\YOUR-AZURE-PROJECT\Sites\$(ProjectName)" /S /Q
Post-Build
%WinDir%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe "$(ProjectPath)" /T:PipelinePreDeployCopyAllFilesToOneFolder /P:Configuration=$(ConfigurationName);PreBuildEvent="";PostBuildEvent=""; PackageAsSingleFile=false;_PackageTempDir="$(ProjectDir)..\YOUR-AZURE-PROJECT\Sites\$(ProjectName)"
The pre-build event cleans up any files left around from a previous build. Â The post-build event will trigger a local file system Publish action via MSBuild. Â The resulting published files going to the “Sites” subdirectory.
Finally, be sure to update the physicalDirectory element in ServiceDefinition.csdef to reference the local publishing subdirectory (notice the ‘Sites’ directory in the updated snippet below).
<Site name="WebApplication1" physicalDirectory="..\..\Sites\WebApplication1\"> <Bindings> <Binding name="Endpoint1" endpointName="Endpoint2" /> </Bindings> </Site>
When you “Publish” the Windows Azure project, all your web sites will build and publish to a local directory. Â All the web sites will have the files you would expect.
Final Helpful Info
When deployed to Windows Azure, the secondary sites referenced in the physicalDirectory attribute are placed in the sitesroot folder of your E or F drive. The site that is the web role is actually compiled, published and deployed to the approot folder on the E or F drive.  If you want to see this layout locally, first unzip the .cspkg and then unzip the .cssx file (in the extracted .cspkg).  This is the layout that is deployed to Windows Azure.
Special thanks to Paul Yuknewicz and Phil Hoff for their insightful feedback and assistance on this post.




Howdy! It looks as though we both have a interest for the same thing.
Your blog, “Tips for Publishing Multiple Sites in a Web Role « Michael S. Collier’s Blog” and mine are very
similar. Have you ever thought about authoring a guest post for a
similar blog? It will unquestionably help gain publicity to your website (my site recieves a lot of targeted traffic).
If you’re interested, email me at: mitchatwell@t-online.de. Thank you so much
Excellent article. This addresses an issue I was having with Azure randomly reverting any website publishings (as opposed to azure cloud service publishings). Hopefully the folks over at Microsoft will develop better support around this feature. Hosting multiple websites on a single role is very common.
Super helpful, thanks Michael. One thing I would add is that when copying/pasting the Post-Build event above (and replacing YOUR-AZURE-PROJECT with your Azure project name), you might get an error when building “Project file not found.”
This was occurring, because the copy/paste ommitted a crucial space in the MSBUILD.EXE arguments, and it looked like this:
“$(ProjectPath)”/T:PipelinePreDeployCopyAllFilesToOneFolder
Ensure you add a space between “$(ProjectPath)” and /T
One quick question – how do we test this from a staging site?
Let’s say I’m hosting two sites, called “mysite1.com” and “mysite2.com”.
Once I deploy my cloud project to Azure, I get a url like: mysites.cloudapp.net
How do I test the two sites, “mysite1.com” and “mysite2.com”?
I’ve tried adding the IP of mysites.cloudapp.net to my HOSTS file, along with entries for http://www.mysite1.com and http://www.mysite2.com, but the request eventually times out in IE and I get the “Internet Explorer cannot display the webpage”.
I’ve even tried it from remote desktoping to the Server 2012 instance in Azure, and changed the host files there, opened IE, and tried to access the sites. Still no luck.
I’ve confirmed my hosts file matches the hostHeader entries in ServiceDefinition.
Any thoughts?
Thanks,
Matt
Please excuse the typos above- my hosts file looks like this (where 1.2.3.4 is the IP address of mysites.cloudapp.net)
1.2.3.4 http://www.mysite1.com
1.2.3.4 http://www.mysite2.com
You should be able to specify the host header (www.mysite1.com) in the ServiceDefinition.csdef file. Once you do that, and update your local host file to have the correct IP mapping (e.g. 127.0.0.1 http://www.mysite1.com), you should be good to go.
One thing I’ve noticed though is that once you do this, you can’t use the Visual Studio debugger. You have to start without debugging.