In the beginning of 2001, a group of seventeen industry leaders who were looking for an alternative to the traditional, heavyweight software development processes convened in the mountains of Utah and authored the Manifesto for Agile Software Development. This document has become the foundation for Agile development, where individuals and team members, actual working software, collaboration, and responding to change is valued more than processes, tools, heavy documentation, contracts, negotiations, and rigidly following a big, up-front plan.  

Within the Manifesto is a set of a dozen guiding fundamentals, known as the 12 Principles of Agile. Like many modern day software shops, MercuryWorks employs the Agile methodology (and mindset) to build enterprise software for our clients. However, since this Agile business has been around for over 20 years, the founding principles aren’t referred to as much as they once were when this was all new, so we’d like to take a moment and take it back to the roots.  

Below are the 12 Principles of Agile, and how MercuryWorks uses these concepts daily: 

Agile Principle #1: Our highest priority is to satisfy the customer through early and continuous delivery of valuable software.

MercuryWorks solves wicked problems. Ridding businesses of their problems is of the utmost value, and our teams deliver software rapidly—almost immediately upon engagement. If you are foreign to the Agile way, a major concept is its iterative nature. We break large chunks of work into small, manageable pieces, so we are able to deliver (and show) working software every two weeks, and in some cases daily. 

An agile team at the board planning delivery

Agile Principle #2: Welcome changing requirements, even late in development. Agile processes harness change for the customer’s competitive advantage. 

Because we are Agile and therefore very iterative, we are built in a way that allows us to welcome change. We don’t plan for an entire calendar year. We plan for a Program Increment (~6 weeks), and then go to work. Within the Program Increment is two or three 2-week sprints, so if a client has a changing requirement, we are able to pivot in the next iteration.  

Agile Principle #3: Deliver working software frequently, from a couple of weeks to a couple of months, with a preference to the shorter timescale. 

MercuryWorks holds this principle near and dear. It is a standard practice of ours to host a Sprint Demo, every two weeks, at the end of an iteration to show our clients what we have built and solicit feedback. Clients will be able to see and touch their investment, and then publish it “live” to production after giving a blessing during the demo.  

Agile Principle #4: Business people and developers must work together daily throughout the project. 

We subscribe to daily communication. We do this in several ways. First and foremost, our developers have a team meeting every morning (Daily “Scrum” or “Standup”). During this ceremony, the team members talk about what they accomplished the day before, what they plan on starting (and finishing) today, and any issues that they may be facing. This allows us to get feedback to the business, almost in real-time.  

Mercury also uses skilled Product Managers to act as the business liaisons. Our Product Managers are intimately in-tune with their clients, and are able to speak on their behalf should the development team have questions about requirements.  

Agile principles require business people and developers to work together to achieve results

Agile Principle #5: Build projects around motivated individuals. Give them the environment and support they need, and trust them to get the job done. 

MercuryWorks has many tenured developers who have been with the company for years. When we hire new team members, we have a rigorous interview process to ensure we are only hiring rock star consultants. Our teams of experts become trusted as a whole, and are self-organizing in many ways.  

During our P.I (Program Increment) Planning, the individual teams present business plans to Mercury leadership, and once given sign-off, are given the environment to get work done, avoiding lag time by micromanagement. Our expert teams are then enabled to do what they do best, and that is deliver high-quality software in short, iterative bursts.  

Agile Principle #6: The most efficient and effective method of conveying information to and within a development team is face-to-face conversation. 

The world has changed a lot since 2001. As a matter of fact, the world has changed a lot in the last 12 months. That being said, with the software development workforce being predominantly remote, “face-to-face” isn’t as commonplace as it once was. However, we heavily utilize video conferencing tools (Microsoft Teams, namely), meet daily, with cameras on, so that we can see each other’s smiling faces and talk about the most valuable (client) things…for that day 

We give the same treatment to our clients: dressed to impress, cameras on, and we meet often.  

Agile Principle #7: Working software is the primary measure of progress. 

In the Client Services space, this isn’t as cut and dry, but at the end of the day, we are a development firm, so our software speaks for us. We believe in this principle so much so that we have built our own custom software to keep clients up to date with progress. We have a tool called MyMWKS, which is a portal that each client has access to that delivers real time project metrics. At the end of each sprint, we actually have a feature called Progress Reports that emails a client with a list of all the new features that were developed, and what they can expect to see in the Sprint / Product Demo.  

Agile Principle #8: Agile processes promote sustainable development. The sponsors, developers, and users should be able to maintain a constant pace indefinitely. 

MercuryWorks has a very mature process which drives pace. “Program Increments” were mentioned earlier, but this is the ceremony where we lay out all of the work we wish to accomplish over the next 6 weeks. While that “P.I” is in-flight, we are working with our clients on initiatives that will be in the very next P.I.  

We meet with our clients weekly to gather requirements and refine all of the items in the Product Backlog. We then meet with our clients bi-weekly to show off the features that are ready to ship. This keeps us in a perpetual feedback loop, where communication and collaboration run high, and the pace is organically sustainable.  

Agile Principle #9: Continuous attention to technical excellence and good design enhances agility. 

Mercury prides itself on its technical acumen and expertise. We use industry best practices, utilize cutting-edge technologies, employ the latest software tools, and have some wicked smart developers on our teams. Our DevOps practice, coupled with our Agile mindset, enables the teams to pivot on a dime.  

Agile Principle #10: Simplicity—the art of maximizing the amount of work not done—is essential. 

Our professionals think in MVP terms (Minimally Viable Product)—by definition, the smallest body of work that we can ship but still deliver value. Large, complex enterprise software systems and the list of features associated can spiral out of control rather quickly. 

This is why working with a professional services firm becomes important, as our trained staff is able to compartmentalize, dissect, and focus on the things we need to do today, leaving all the work we don’t need to do until tomorrow where it belongs: in the product backlog.  

building custom software according to Agile principles

Agile Principle #11: The best architectures, requirements, and designs emerge from self-organizing teams. 

“Self-organizing” teams have been mentioned earlier in this piece, but we’ll give it some greater attention here. Historically, the traditional pyramid-shaped enterprise slowed development progress. When there are massing “Change Order” and approval processes in place, it makes it difficult to get actual software features done.

Mercury’s team of rock star professionals have embraced the self-organizing cultures to focus on the most important metric: delivering working software.  

Each team has a Product Manager who becomes an extension of our client’s business. Each team also has every necessary (technical) discipline to be self-sufficient. So, when the Product Manager is able to be the proxy-business owner, and there is a team with all of the tools, the only thing left for us to do is organize and deliver.   

Agile Principle #12: At regular intervals, the team reflects on how to become more effective, then tunes and adjusts its behavior accordingly. 

MercuryWorks does this both internally and externally. At the end of each sprint, the internal development teams host a ceremony commonly known as the “Retrospective.” Every two weeks, we take a look at the sprint that has just passed, assess what went really well (so that we can keep doing that), and what could have gone better (so we can solve that issue and avoid a repeat).  

We utilize a similar format with clients. It is typical for Mercury to meet with its clients to do monthly reviews (of software features, roadmap, progress, budgets, future projects, all the things, etc.), and during that session we have made a practice of retrospectively analyzing the last month together. What went well, what should we keep doing, what could have gone better, and what do we do (together) to improve? 

While Mercury didn’t invent the Agile methodology, practice, and mindset, we sure drink the Kool-Aid. This is a proven way for groups to work together and to foster collaborative environments. We’ll continue to ride the Agile bus as we create great apps, solve wicked problems, and forgepassionate client relationships. 

The Product Owner / Manager role is a new(er) concept in the grand scheme of things. As a matter of fact, I didn’t even know that this role existed when I was in high school and college, as its definition was just being authored, but yet here I am. There are countless articles and blogs around the topic of Product Owner, but considering its contemporary nature, there is also a lot of confusion around who and what a Product professional really is. In a sea of opinions, the sake of this note is to summarize why MercuryWorks champions the Product role.  

What are the textbook responsibilities of a Product Manager?

According to the Scrum Guide, the Product Manager is accountable for maximizing the value of a Product. That (somewhat ambiguous) statement is humbly followed by, “How this is done may vary widely across organizations.” The guide then goes on to list the other responsibilities of the Product person, such as:  

  1. Developing and explicitly communicating the Product Goal 
  2. Creating and clearly communicating Product Backlog items 
  3. Ordering Product Backlog items 
  4. Ensuring that the Product Backlog is transparent, visible, and understood  

How does MercuryWorks explicitly apply this in the real world?

Yes, the primary role of the Product Manager is to “maximize value”. Value comes in many shapes, however, and building a new software product is an extremely dynamic venture. There are timelines to manage, budgets to track, features to flesh out, evolving industries, competing priorities, stakeholders to manage, among many other things. The Product Manager is indeed accountable…for all of it. So, in short, the maximum value that our Product Managers add to Product Development is ensuring it all happens, and happens as smoothly as possible.  

Our Product Managers certainly are responsible for communicating the Product goal to the rest of the (development) team members, and we do this by spending a lot of time with our clients. Not incessant, wasteful time, but investing in rich conversations with our client’s stakeholders, so that we understand their business, their needs, their vision, their industry, their goals, and the end-game. We treat it like our own.  

While investing time into intimately understanding our client’s business and goals, we’re authoring a Product Backlog (list of features that make up the Product as a whole), and “ordering” those items (prioritizing in rank of importance), so that the rest of the development team has a pecking order. Our Product Managers are deeply in-tune with the team, guaranteeing that desired outcomes are always understood, and we do this through actual conversations (our teams meet daily, and sometimes hourly, to dive into details as a unit). In essence, we keep the team fueled with things to work on and features to ship.  

The more implicit side…

There is plenty of formal training out there (which our Product Managers have been traditionally educated in) that instruct one how to “do” Product, but only experience reveals how to “be” Product. Aside from the textbook definitions, the Product Manager is a translator, a negotiator, a psychologist, and an entrepreneur. MercuryWorks brings this diversity to the table.  

Our Product folks translate for a living. We talk business, we talk operations, and we definitely talk tech. And, we adapt to our audience, so that we keep the “transparent, visible, and understood” spirit thriving. Our Product team members are expert negotiators. There are a lot of decisions to make while developing custom software, so continuously working out win-win scenarios is a true art. Building custom software is also a very complex process, and sometimes it is the Product Manager’s job to talk stakeholders through the process, to make them feel confident, and to help alleviate any anxieties.  

Finally, an exceptional Product Manager thinks like an entrepreneur. Product Managers need to fully understand the impact of a given software product on a business. They need to understand true “business value”, whether that be enhancing customer delight, driving licensing revenue, adding customers, automating a manual process (and saving overhead), or disrupting a static industry with new technologies. Software is a big investment, so our Product Managers are equipped to understand how software is meant to be monetized, and to ensure our clients are truly getting the highest return on their investment.  

Final thoughts…

The world of software development is getting bigger. There are more technical options than ever, the world is picking up speed, and having a firm with a strong Product game can help you navigate those complex waters. MercuryWorks has decades of software development expertise, industry competence, and Product know-how to bring your visions to life.  

You can read more about our up-front Strategy and Planning sessions, where we do an introductory deep-dive into our client’s business and start to formulate a Product Roadmap here.  

Thank you for reading!  

Two pillars of a solid DevOps strategy are Continuous Integration and Continuous Deployment (CI/CD). At Mercury we have been utilizing Azure DevOps for our CI/CD process and have seen the implementation of Pipelines change and continuously improve over time. The trend has been towards a fully scripted pipeline that can be included in version control along with the code and infrastructure.   

This post will explain how to set up an end-to-end pipeline using multi-stage pipelines in YAML. As there are several moving parts, it’s helpful to have an example of the process so that you can follow along.

We assume some working knowledge of Azure DevOps, Azure, and general DevOps concepts. While we’re deploying a .Net Core project, you don’t need to have previous .Net Core knowledge. The concepts of creating the pipeline are universal for all supported languages.

Table of Contents

  1. What You’ll Need to Build a Pipeline
  2. Setting up the Azure Devops Pipeline in YAML
    1. Plan the Build
    2. Start Writing Code
    3. Start Testing the Build
    4. Finish the Build
  3. Azure Pipeline Deployment
    1. Set Up Azure App Services Connection
    2. Set up the Deployment Pipeline in Staging
    3. Deploy to Staging
    4. Deploy to Production
    5. Configure Environments

1. What You’ll Need to Build a Pipeline 

  • Azure subscription (free to sign up) 
  • Azure DevOps account (free to sign up)
  • A Git repository; any repo can be used and connected to Azure Pipelines, but we’ll use Azure Repos
  • An Azure App Services Plan (free tier) with two app services.
  • An IDE. This walkthrough was created using Visual Studio Code, which has extensions for Pipeline syntax highlighting 
  • A base project. We will be deploying a .Net Core API project throughout this series, and you can find our base project here.

2. Setting Up the Azure Devops Pipeline in YAML

The Microsoft documentation for Azure Pipelines has a good breakdown of the pipeline hierarchy and the supported YAML syntax.

Here’s a brief example of the structure of a multistage pipeline:

  • Stage A
    • Job 1
      • Step 1.1
      • Step 1.2
    • Job 2
      • Step 2.1
      • Step 2.2
    • Stage B

A pipeline is comprised of Stages, Jobs, and Steps. A stage contains multiple jobs and jobs contain multiple steps. The YAML syntax following the outline above would be: 

Just be sure to keep an eye on the required indents and dashes when creating a pipeline. There are syntax checker add-ons in Visual Studio Code that can help prevent errors. 

Plan the Build 

For those familiar with the current setup of Azure Pipelines, our end goal is to create the artifact that will be deployed. 

This is the plan for the steps needed to create the final artifact:

  1. Install build requirements
  2. Restore dependencies (in this case, NuGet packages)
  3. Build
  4. Test
  5. Publish (create application packages)
  6. Create build artifact (to be used in future stages)

For this part of the pipeline, we will go ahead and put all these steps in a single stage and a single job. Multiple jobs will allow you to run those groups of steps in parallel which isn’t necessary here – all the steps are dependent on the previous step.

Start Writing Code

Create a file in your project with a .yml extension. There is not a required name or location for the file. We usually recommend creating a folder at the top level for it and naming the file something like “pipeline.yml.”

We know there will be one stage, one job and up to six steps, but let’s start with just the first step.

A few notes here:

  • The internal name of stages, jobs and tasks do not allow spaces and are not always descriptive. displayName allows for more description and transparency when the items are displayed in Azure DevOps.
  • pool/vmImage refers to the virtual machine (agent) the build will run on and we have two options: a Microsoft-hosted agent or a private agent. We’ll go with Microsoft for now as we can get one for free. There are various images available to choose from. In the example above, we are asking for whatever the latest version of Windows is available.
  • The first task is the Dot Net Core installer task. We want to make sure that a specific version of the .Net Core SDK is installed that is compatible with our application. The syntax ‘3.x’ is used to specify that it should install the latest of major version 3. (There are a wide variety of tasks available in Azure DevOps by default and available through the marketplace.)

Start Testing the Build

We’ve just started building the pipeline, but let’s take a quick detour and go set up the pipeline in Azure so we can start testing as we go along:

  1. Build a New Pipeline. In Azure DevOps, select ‘Pipelines’ in the navigation and then ‘New pipeline’.
  2. Where is your code? We’re using Azure Repos Git here, so setting up a connection to a different repository doesn’t apply, but once the connections are made the pipeline setup will be the same.
  3. Configure your pipeline.There are options for some pre-made builds, which can be useful starting points. For now, select Existing Azure Pipelines YAML file. If you don’t see it in the default list, then select the Show More button. In the window that opens, select the branch and the path to the YAML file for your pipeline. The path dropdown will pick up on appropriate files in your project.

  1. Run. Once you’ve selected the file, click the blue Run button.
  2. You’ll see a screen with the build information and a drill down into the currently running job. On these screens you can see how the displayName property that was set is used.

If you have a passing build, congratulations!

If you don’t have a passing build, it’s time to troubleshoot.

First, double check that the syntax in YAML is correct. Here is an example where there was a missing space after the dash in ‘- job’ on line 5 in the script just used. No drill down is available because the pipeline never executed with this error.

Finishing the Build

Only one task has been added so far to our script. Let’s add the additional tasks.

Some additional notes:

  • You will notice that there are fewer steps in the script than what was outlined above. The .Net Core publish command does the restore, build, and publish all in one step.
  • The ‘Publish Artifact’ task is different from the .Net Core publish It will allow the artifact to be available to other jobs in the DevOps pipeline (as you’ll see later).

Check in the code, and then in Azure DevOps watch the update pipeline run. It was set up previously and for now, it will automatically run the pipeline on any check in.

The success screen you see will be the same with a few new pieces of information:

• Under Related, you will see that there is one published item. This is the artifact that was created in the last step of the pipeline. Clicking on the link will allow you to see the full structure and download any files. This can be useful for debugging if all the correct files were included.
• Tests and coverage: The test project includes a single test (which hopefully passed).

Some Extra Credit: Triggers, Name, and Variables

While not critical to building a basic multi-stage pipeline in Azure DevOps, adding a build name, triggers, and variables add some helpful functionality. Here’s an example of what they look like added in to the YAML file:

Specifying triggers will reduce how often the pipeline is run. This file directs Azure DevOps to only run the build on pull requests created for the master branch and on a merge to the master branch. We have branch policies in place to require a passing build on Pull Requests.

name creates a unique name for the build. By default, it sets the date and the unique build ID in Azure. In the example below, the default has been overwritten to format the date differently and add the branch name. This can be modified to the format desired for your team.

Finally, variables are pipeline-specific properties that can be reused throughout the file. A variable is referenced using $(variableName) syntax. For example, in the YAML file above the AgentImage has been converted to a variable and referenced using $(AgentImage). While it is currently only used in one place, this will become useful as we extend the pipeline.

It’s possible to stop here and only include the build in YAML, then continue using the existing Azure DevOps Releases UI. But it’s also possible to expand the pipeline so that the deployment steps are also included in the code.

Let’s do that next.

Ready to tackle technical debt?

Take our free email mini-course to find out how:

3. Azure Multi-Stage Pipeline Deployment in YAML

Next, we’ll deploy the packaged code from our build above to two different app services—we’ll call them “staging” and “production”—with the appropriate dependencies between the stages. In true DevOps fashion, we’ll also set a pre-deployment approval check before deploying to the production infrastructure.

Setting Up Azure App Services

In order to deploy the code, we will need a place to host it. If you haven’t yet set up your free Azure App Service plan, go ahead and do that now.

Next it’s time to create Azure resources in Visual Studio Code for both staging and production environments:

  1. Install the Azure App Service extension
  2. Hit the F1 key and do a search for ‘Azure App Service create’
  3. Select Azure App Service: Create New Web App (Advanced)
  4. Sign into your Azure account
  5. Follow the steps to create an App Service for the staging environment
    • Environment OS must be Windows
    • The App Service Plan can be the free tier
  6. Create another new app and repeat the steps to create an App Service for the production environment
    • Use the resource group previously created
    • Use the App Service Plan previously created

One additional setup piece that needs to happen is to create a Service Connection in Azure DevOps to your Azure account.

If you have the appropriate permissions in Azure and Azure DevOps, you can complete this automatically. If not, follow these instructions to set it up manually:

  • In the Project Settings, select Service Connections
  • Create a new Service Connection
  • Select Azure Resource Manager
  • For the authentication method, select Service principal (automatic)
  • Select the appropriate subscription and enter details
  • Select Grant access permission to all pipelines and save.

Set Up the Deployment Pipeline in Staging

Now that setup is out of the way, we can get back to setting up the pipeline! First we’ll get the code to the staging instance.

Remember that a pipeline is a collection of stages. Stages can run sequentially or in parallel depending on how you set dependencies up (more on that later). Jobs in a stage all run in parallel and tasks within a job run sequentially.

Why Run Jobs in Parallel?

The applications we work on at MercuryWorks all have functional tests and infrastructure as code which need their own package of files to be sent to the Release. In the build stage we end up having three different jobs: one to build and create the application artifact, one to build and create the functional test artifact, and one to create the infrastructure artifact. They all run in parallel, which reduces the overall time to complete the stage.

Right now, we only have one stage for the build with the last step creating an artifact of the built code. The tasks to deploy this code to the staging infrastructure will be in a separate stage.

This stage will have a few new concepts compared to the build. Click here to see the code in Git. Let’s see what the stage looks like (don’t panic!):

  • The first major difference you’ll notice from the build stage is instead of a job listed under jobs it is instead named deployment (line 8). This is a specially named job that allows for additional options than a standard job type, including deployment history and deployment strategies.
  • A bit further down there is a property named environment (line 12)This is set to ‘Staging’ because that is what we are naming this environment and in the deployment stage to the production instance it will be named ‘Production’. These environments can be named according to your own environment naming strategy. More on that later.
  • The strategy section (line 13) has a variety of lifecycle hooks (also specially named jobs) that can be used in different deployment strategies. You can find a description of all available options here. For this walkthrough we are using the simplest strategy of RunOnce. In RunOnce, each of the lifecycle hooks are executed once, and then depending on the result an on: success or on: failure hook is run. Our application is very simple so we’ll only use the deploy. 
  • Each life cycle hook has their own set of steps (line 16) to execute. At this point things should look a bit more familiar. First we want to extract the files from the zip that was created in the build, then deploy those files to an Azure App Service.


At this point, the package locations in the extract files task and the package in the deploy step are not filled in yet. We’ve set up the build which created an artifact that needs to be referenced here. Let’s add three more lines and fill in the package location details:

  • The deployment stage just added should not run before or in parallel with the Build stage because it needs the artifact created. dependsOn (line 7) provides an array of stages that this stage should verify have successfully completed before running. Using this array on each stage will help arrange the pipeline to run exactly in the order you need. Note that this needs to match the name set to the stage: property, not the display name.
  • Typically we want artifacts from the current context – the run that is currently happening, not a previous run. download(line 18-19) is a specially named task that will download artifacts created from previous stages. The artifact specified to download is the one created in the Build stage (it was named ‘app’).
  • Now we can tell this task where to find the zip file: archiveFilePatterns/destinationFolder (lines 27–28). The location where artifacts are downloaded to is contained in the variable $(Pipeline.Workspace). This structure was defined in the build, and we can refresh our memory of it by reviewing the artifacts created from the last build. Here we’ll extract files to a new directory and specify a files 
  • The Dot Net Core publish task put all of the files inside a package(line 32) named the same as the project, which is why there is the extra folder inside the files folder here. 

Deploy to Staging

The pipeline is now at a point where we can test it out. Here is what the full pipeline should look like now. Let’s commit the updates and watch it run.

Checking on the build, there are some UI changes now that the second stage has been added:

Clicking into the pipeline, it now shows both stages. Notice the ‘Build’ stage indicates that it has 1 job (0/1 completed as it is currently running). Within the stage is the Application Build job. If there were more jobs within the stage, they would also be listed here.

If you do not see the job list, hover over the stage and click on the up/down arrow symbol that will show up in the top right corner of the box. Clicking into a job will give a further break down of each task and logs.

Once the pipeline has completed, head on over to your site! The endpoint for this will be This sample application has no endpoint at the root level.

Deploy to Production Environment

The final stage in the pipeline is to deploy your code to the production App Service. It will be similar to the previous stage we created with a couple exceptions:

  • Make sure that the stage and job names (as well as the name of the web app being deployed to) are all updated to indicate they are for production.
  • The dependsOnsection has been updated to indicate a dependency on the build stage as well as the staging stage. We don’t want production being released before (or even at the same time) as staging.

As an example, this is what the pipeline would look like in Azure DevOps if the production stage only had a dependency on the build stage (dependsOn: [‘Build_Stage’]).

Notice that the dependency lines show that both staging and production will run at the same time after the build stage has completed? Instead, let’s make sure that the production stage has all the proper dependencies and commit the code.

Congratulations! Your application has been deployed to all environments.

Configure Environments

Before we celebrate too much, there is one last thing we need to do. If you watched the pipeline run, you would have noticed that the production stage ran immediately after staging. While some projects may be able to do that with an appropriate number of tests, most of the time we prefer to have an approval step in between stages.

At MercuryWorks, we use the staging environment to demo new functionality to clients and like to have a bit more planning around when new code is deployed.

This is where Environments come in.

In Azure DevOps under Pipelines in the navigation, there is a section named Environments. After clicking on this, you will see that there are already some environments listed. These were automatically created when the environment property was added to the pipeline script.

This is a nice, quick way to determine what version of the application is deployed to each environment and what pipeline run it is related to.

Now that those environments are defined, we can set approval gates. When in a specific environment, click on the three-dot menu in the top right and select Approvals and checks.

There are multiple types of checks that can be set for an environment. We are only going to be adding an approval for this pipeline, so we’ll select Approvals. On this form you can add specific users and/or groups to the list of Approvers. Fill out the approvers and click ‘Create’.

Head back to the pipeline and select Run pipeline in the top right. Leave the default options, select ‘Run’ and let the pipeline run. Once Staging completes, you should now see Production marked as ‘Waiting’ and the person you set as an approver should have received an email. Logging in as the Approver, there will be a Review button above the pipeline flow.

Clicking into Review, the Approver can ‘Approve’ or ‘Reject’ the deployment and add an optional comment.

Once approved, the Production will run as normal. Congratulations! You now have a full pipeline in YAML with multiple environments and approvers.

Next Steps

This should get you started on creating YAML pipelines in Azure DevOps. There are many ways to customize these pipelines, including adding variations and themes. The important thing to remember is that having a fully scripted pipeline helps reinforce the important foundations of a DevOps practice, making automations and approval processes easier and systems more secure.

How to Manage an Effective Digital Transformation in Changing Times

Tech leaders are facing new challenges in 2022: more pressure than ever to keep systems thriving in the midst of huge technical demand… and the threat of losing the skilled labor required to do it. In this exclusive series, we’ll share some key takeaways to help tech teams stay agile:

  • Why digital transformation is an insurance policy against attrition and change
  • How to identify and manage the technical debt that threatens employee productivity and fulfillment
  • How DevOps practices can liberate and improve tech teams
  • The playbook we use ourselves at MercuryWorks to create painless digital transformations for our enterprise clients
strategy planning for a custom application

Looking for a faster, cheaper, and easier way to build, manage, and maintain a mobile app for your organization? The benefits of a progressive web app might be the answer.

Traditional native mobile apps like those launched in the Apple or Google Play app stores can be a bit involved to build, manage, and maintain. Depending on the operating system (iOS or Android), they require a deep working knowledge of a variety of highly specialized programming languages. 

If you’re planning on releasing the app across different types of devices, you will essentially need to build two separate apps (or figure out a way to translate one to the other by building a cross-platform app, which is almost more labor-intensive). This translates into duplication of development work across code bases and a variety of complications: more cost and more time building, managing, and updating separate apps.

Luckily, there’s a better way. Progressive web applications offer many of the benefits of native, but without the complexity and cost.

What is a progressive web app?

To the user, a progressive web app (PWA) looks and behaves just like any other mobile app. It can be accessed by its own icon, and when open it’s virtually indistinguishable from a native app. The difference is that the PWA is running in a browser—which makes it easier, faster, and cheaper to build and support.

Progressive web app examples

Because progressive web apps can be accessed by any browser, you likely interact with them every day without even realizing it. For example, Starbucks uses a progressive web app to allow users to log in, see the menu, and order items directly from their web browsers.

At MercuryWorks, we recently worked with the i9 Sports team to redevelop their online presence as a PWA. App users can log in to the app to find programs, check their schedules, and see practice plans from their mobile devices and desktop computers. The application provides the same great user experience across screen sizes.

A progressive web app from Starbucks


i9sports progressive web app

i9 Sports

The “progressive” in progressive web apps comes from the use of service workers, code scripts that can approximate a native app experience by allowing users to access the app even while offline. In the Starbucks PWA, for example, ordering obviously isn’t possible without a connection, but users can still be able to open to the home screen and read the beverage menu—something common in native apps but relatively unheard of in web browsers.

Four key benefits of progressive web apps

There are a few tradeoffs for choosing a progressive web app over a native app, but in many instances a PWA is a more effective solution for company and user needs because it is easier to create and maintain. Here are the main benefits.

1. Less development time

Unlike the different coding languages required for native apps, PWAs are built in JavaScript. JavaScript is a fundamental programming language used by developers worldwide—often the first language they learn after HTML and CSS—and it is used in most web apps across the Internet. Being able to code an app in JavaScript rather than Objective-C, Swift, Java, or Kotlin (or a combination of those and others) saves a substantial amount of developer time.

Another benefit is that in a progressive web app, there is one code base for the app, rather than separate code bases for iOS and Android versions. That means one set of updates rather than multiple and less troubleshooting across platforms—and as PWAs are web based, updates to the app happen automatically and don’t require users to download them to access the latest security features or improvements.

2. Better user experience

By relying on service workers, progressive web apps boast a lightning-fast loading time and mobile-first design, delivering a smooth and optimized experience to users. Adopting these best practices is often rewarded on search engine results pages, especially with Google’s Core Web Vitals indicating the importance of user experience and site speed to the latest ranking algorithms. 

And unlike traditional websites, PWAs can take advantage of the native app-like experience to increase engagement and conversion rate. For example, an application can send users push notifications to remind them to sign in and engage.

mobile phone with progressive web app

3. More accessible and easier to launch

It’s not easy to launch an app in the Apple App Store. There is a tedious approval process with high (and, some would argue, arbitrary) standards for acceptance, along with high competition with other apps. In addition, having to visit an app store (iOS or Android) to download an app adds another required step in the buyer journey. Requiring potential users to visit an app store and follow through on their intent to download the app creates friction and may lose potential users in the process. By contrast, a progressive web app can be downloaded directly from anywhere—a company website, an email attachment, or anywhere in the cloud.

To be fair, there are a few tradeoffs that make native apps a better choice for app store launches. If your app depends on integration with other native mobile apps (such as calendars or social media apps like Facebook) or in-app purchases that would normally be handled through an app store, a native app might be a better solution. And if your heart is set on launching in an app store, a progressive web app adds some complexity to that process as it requires extra steps for approval.

But for internal apps and a large array of customer-facing ones, the benefits of an app store launch don’t outweigh the drawbacks of an easier development process and a simpler launch process. In these cases, a progressive web app is a practical, lightweight solution to your needs.

4. Save on development costs

Both above advantages are benefits in themselves, but progressive web apps also translate into lower costs for development and maintenance. Time is money in many aspects of business, and software development is no exception. Why build a native app in multiple codebases, resulting in up to twice the development costs, for no real advantage? Why make your app more difficult and expensive to update and maintain, requiring even more developer or DevOps professional time, when you could invest in a process that’s easier to support internally?

The truth is that if a more lightweight progressive web app can accomplish what you and your users need, it is a far wiser, better use of company resources. By saving developer time and simplifying the process, your company could benefit significantly in terms of cost savings and ROI. And because PWAs are easier to maintain and extend, that time and money saved extends into the future with quicker, less complicated updates.

Is a progressive web app right for you? Pros and cons

While progressive web apps offer a lot of benefits, there are a few tradeoffs that make native apps a better choice in some cases: 

  • If your app depends on integration with other native mobile apps (such as calendars or social media apps like Facebook) or in-app purchases that would normally be handled through an app store, a native app might be a better solution. 
  • If your heart is set on launching in an app store, a progressive web app adds some complexity to that process as it requires extra steps for approval.
  • If you want your app to work with device features like Bluetooth, fingerprint scanning, and advanced camera options, a progressive web app might not be the best choice.

Sometimes these tradeoffs are non-negotiable, but often the pros of a progressive web application far outweigh the negatives. This is why consulting with an expert team on a software development plan to outline your business needs is absolutely essential and will result in a solution that balances performance, cost, and features.

Pros and cons of a progressive web app

Interested in determining if a progressive web app is the right fit? 

At MercuryWorks, we can guide you through each step of the process and advise you on what type of development solution will work best for your business needs. 

Start the conversation here and we’ll reach out shortly:

"*" indicates required fields