Previously at work, our marketing websites were all designed up front, with copy provided by our colleagues in Marketing and then the pages were generated by the development teams. Because this was taking our developers away from the tasks where they can add real value to the customer journey by streamlining the end-to-end process, it was decided that the company would benefit from introducing a CMS solution with which the Marketing team and Content Writers would be able to manage content themselves.
At first this seemed really straightforward, but we had the following criteria which the solution had to abide by:
- The end solution should stop copy changes being provided to developers as a change request.
- The Marketing and Content users shouldn’t be expected to know any code/markup in order to make new pages.
- The content shouldn’t be served publicly via the CMS, but rather as templates through Tailor which we currently use to serve our web content.
After some discussion, we came up with the following requirements:
- We needed a database in AWS for storing Umbraco data.
- We needed an Elastic Beanstalk instance configuring on AWS for hosting our implementation of Umbraco. This was preferred over a single EC2 instance as it would give us load balancing out of the box.
- It was essential that we be able to develop “widgets” in an agile approach, so we had to be able to deploy changes via GoCD.
- Given that we would be hosting it on an Elastic Beanstalk set up, we needed to store our media files (images etc.) externally to the Umbraco site, so they were kept safe elsewhere but publicly accessible.
We used the following toolset:
- Umbraco CMS (with a few plugins - details below)
- Cake build
Creating the project
We created an empty ASP.NET Web Application using the .NET Framework (not dotnetcore) and installed Umbraco via NuGet.
and we then installed some further packages:
Umbraco-S3-Provider (to allow Umbraco to treat an S3 bucket as a local storage device for storing media)
uSync (a content synchronisation tool which tracks developer changes)
Chauffeur (a package delivery system - to aid with initial installs)
uSync Chauffeur (a uSync plugin to allow Chauffeur integration)
Initial Umbraco setup
We were able to run the project through Visual Studio which ran the application through IIS Express and go through the initial setup process (configuring database access and a user to access the administrator panel).
At this point, we took a snapshot in time using Chauffeur. This generated a few configuration files (see below) that we keep in source control, but we inject our administrator password in via the deployment pipeline (using environment variables) in order to to keep credentials out of source control.
This file is created by the initial Chauffeur run, but we had to make some minor tweaks to allow the password to be injected through GoCD.
install y user change-password admin $adminpwd$ package 001-Setup
$adminpwd$ variable is populated on the command line when running
This file provides an XML representation of the default data fields that are installed by Umbraco as part of their first run setup/wizard system, I won’t include it here as it’ll do that for you.
AWS setup (Terraform)
For us to be able to create the infrastructure needed in AWS, we stuck with our tried and tested infrastructure as code option: Terraform.
We opted for SQL Server as our database engine, so we created an
aws_db_instance resource in our Terraform scripts.
Also, to make our deployments more straightforward, we created a Route53 entry to make the DB hostname more predictable for later stages of deployment, so we created an
aws_route53_record resource for which we also created an
aws_route53_zone data record to create a record inside our private zone. This record was of type “CNAME” as the
aws_db_instance resource outputs a hostname rather than an IP (which would be needed when creating an “A” record via Terraform).
We created an
aws_elastic_beanstalk_environment resource, and we also created a Route53 entry so that the URL for the Umbraco site would be consistent and the end users would be able to bookmark it for easy access.
For us to be able to run the web application on the EC2 instances, we needed to create an IAM role so that we could restrict the permissions to specific services that were needed for Umbraco to operate. We configured the policy for this role so it had access to S3 (I’ll come to that shortly), RDS (for our database) and Lambda (I’ll come to this shortly too).
The Umbraco-S3-Provider plugin for Umbraco allows us to point our Umbraco instance at an S3 bucket when working with Media files rather than the default of looking at
~/media within the Umbraco directory.
This provides a common place for images to be located which will be unaffected should the EC2 instances get recreated in case of any issues etc.
For this, we had to create an S3 bucket, so we created an
aws_s3_bucket. We also created a Route53 record for this too.
As we intend to serve our CMS-built content statically through Tailor, we have a project that is running as a Lambda function in AWS which we trigger, providing the CMS path, whenever a page is published in Umbraco, so that the published page can be read into a static html file, and stored into an S3 bucket that is used by our Tailor implementation.
Our day-to-day development process
As the content is managed by our content writers, we’re just responsible for creating the tools/widgets within the CMS to allow the creation of pages which will have the biggest impact on our customers.
All development work is carried out on local development environments and changes are committed to source control as follows:
- Changes to document types/templates etc. are watched by uSync and the local files are kept updated as a result which are then committed.
- New templates created inside Umbraco must have the files included in the dotnet project file so they’ll be included in the build and subsequent deployment.
We have multiple environments which receive the deployments via GoCD. As such, we have to take into account the following:
Environment-specific config files
We have environment specific versions of the following files:
~/config/FileSystemProviders.config- this had to be edited as part of the installation of Umbraco-S3-Provider to point at a specific S3 bucket.
~/config/imageprocessor/security.config- this had to be edited as part of the installation of Umbraco-S3-Provider to point at a specific S3 bucket.
To handle these file changes, we’ve used an ebextensions post-deploy script to copy the environment specific configs into the relevant directories at deploy-time.
However, for the
web.config changes, we use transformation configs which are specified in the command when running
Msdeploy.exe using the
Chauffeur & uSync
We “deliver” the Chauffeur package created during initial setup which will set up the initial build of Umbraco and configure the password for the admin account.
Chauffeur.Runner.exe delivery -p:adminpwd=$adminPassword
Once Umbraco is configured, we then sync the installation with the uSync content included in the deployment:
Chauffeur.Runner.exe usync import
So what does this mean for our users?
The result of this is that our colleagues in Marketing and our Content Writers are able to create new content within Umbraco, and when the content is published, it is also published publicly as static content at a pre-defined location, so it can be used in marketing campaigns etc.
We’ve had this set up for a few months now and so far, it has really made a big difference to the company’s ability to make changes quickly which will help us react to the market a lot more efficiently going forward. We’ve also moved our FAQ content to be managed through the CMS too which means we’ve been able to reduce the number of systems our colleagues are having to use and maintain.
It’s not all plain-sailing though…
… as we’ve encountered a few issues during this. In particular when we’re publishing the Umbraco pages out to static content.
The 2 events that are fired during the Publish process (Publishing and Published) aren’t fully suitable for our use case where we are storing the generated pages as static content to be served through Tailor. We are looking into alternative ways to hook into the Publishing process to push our content successfully first time, every time.