How you can master continuous delivery with Vue, Docker, and Azure
As a full-stack developer, I know how critical it is to get new features and fixes out to users quickly and reliably. In today‘s fast-paced software world, long release cycles and manual deployments just don‘t cut it anymore. That‘s why practicing continuous delivery is so important for keeping a competitive edge.
In this in-depth guide, we‘ll walk through implementing a robust continuous delivery workflow for a Vue.js application using Docker and Azure. Whether you‘re new to these technologies or have some experience, I‘ll share my personal tips and best practices to help you ship better code faster. Let‘s jump in!
What is continuous delivery?
Continuous delivery is the practice of automating the entire software release process so that you can confidently deploy new versions to production at any time. The goal is to make releases boring and routine rather than stressful and risky.
With continuous delivery, every code change that passes the automated tests is a candidate for release. Releases happen frequently (often multiple times a day) and are fully automated – from building to testing to deploying.
Studies show that organizations that implement continuous delivery can deploy code 46x more frequently, have a 7x lower change failure rate, and recover from failures 2604x faster than their peers [1]. Developers also report higher levels of job satisfaction and less burnout.
While implementing continuous delivery requires an initial investment, it quickly pays off by enabling faster iteration and learning. Rather than spending days or weeks preparing for a release, developers can focus on writing code and teams can get rapid feedback from real users.
Why use Vue, Docker and Azure?
To setup a continuous delivery workflow, you need several key components:
- A version control system to track code changes
- An automated pipeline to build, test, and package your app
- An image registry to store versioned packages
- A way to test and deploy your app to production
There are many great tools available in each of these areas, but I‘m a big fan of the Vue + Docker + Azure stack for it‘s simplicity and power:
-
Vue is a progressive JavaScript framework for building web UIs. It‘s lightweight, performant, and easy to learn. Vue‘s component-based architecture makes it a great fit for continuous delivery.
-
Docker is the industry-standard platform for packaging and running apps in containers. Containerizing your app ensures consistency across environments and simplifies deployments. According to surveys, 79% of companies are running containerized applications in production [2].
-
Azure is Microsoft‘s comprehensive set of cloud services for building, deploying, and managing applications. Azure provides all the pieces you need for an end-to-end continuous delivery pipeline. 64% of Fortune 500 companies use Azure today [3].
Specifically, we‘ll use:
- Azure Repos for version control
- Azure Pipelines for CI/CD
- Azure Container Registry for our Docker images
- Azure Web Apps for hosting our containerized app
You can substitute equivalents like GitHub, Jenkins, Docker Hub, AWS, etc. based on your needs and preferences. But I find Azure‘s integrated offering makes it quick and easy to get all the pieces working together smoothly.
Running Vue in Docker
First, let‘s dockerize our Vue app for local development. Assuming you already have a Vue project, create a new Dockerfile
in the root:
# base stage
FROM node:lts-alpine as base
WORKDIR /app
COPY package*.json ./
RUN npm ci
# build stage
FROM base as build
COPY . .
RUN npm run build
# production stage
FROM nginx:stable-alpine as production
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
This multi-stage Dockerfile
:
- Starts from a Node.js base image to install dependencies
- Builds the Vue app in a separate stage
- Copies the built files to an Nginx image for serving
- Exposes port 80 and starts Nginx
Now build and run the Docker image:
docker build -t my-vue-app .
docker run -d -p 8080:80 --name my-vue-app my-vue-app
Browse to http://localhost:8080
and you should see your Vue app running in a Docker container!
Packaging your app in a Docker container has many benefits:
- All dependencies are included so the build is reproducible
- The app runs the same in every environment
- You can easily scale the app by spinning up multiple containers
- Deployment is simplified to pushing and pulling Docker images
83% of developers say containerization is important to their work and company [4]. So this is an essential skill to learn for continuous delivery and modern software development in general.
Pushing to Azure Container Registry
To deploy our Vue app, we first need to push the Docker image to a container registry. A registry allows you to store, manage, and secure your Docker images as you would source code.
We‘ll create a private registry with Azure Container Registry (ACR) to hold our images. From the Azure portal, search for "Container registries" and click "Create":
Choose a unique registry name, select your subscription and resource group, and click "Create". Your new registry should be provisioned within a few minutes.
Back in your terminal, log in to the ACR:
az acr login --name <registry-name>
Tag your local Docker image with the ACR server URL and push it:
docker tag my-vue-app <registry-name>.azurecr.io/my-vue-app:v1
docker push <registry-name>.azurecr.io/my-vue-app:v1
You‘ve now stored your first Docker image in Azure Container Registry! Navigate to your ACR in the Azure portal to see the image listed under "Repositories".
Deploying to Azure Web Apps
With our Vue app packaged in a Docker image and pushed to ACR, we‘re ready to deploy it to Azure. We‘ll run the app in an Azure Web App, which is a fully-managed platform for hosting web applications.
In the Azure portal, click "Create a resource", search for "Web App", and click "Create". Fill out the basics like subscription, resource group, name and region. Under "Publish", select "Docker Container". Then configure the container details:
- Image Source: "Azure Container Registry"
- Registry: Select your ACR from the dropdown
- Image: Select the "my-vue-app" repository
- Tag: Select "v1"
Leave the other settings at their defaults and click "Review + create", then "Create". Azure will now provision your web app and automatically pull the specified Docker image from your ACR.
Once the deployment completes, find your web app‘s URL in the "Overview" tab. Browsing there, you should see your Vue application!
Not only is your Vue app now running in the cloud, but the entire deployment process is easily repeatable. Whenever you push a new version of the Docker image to ACR, you can simply update the image tag on the web app to deploy it.
However, we don‘t want to have to manually update the image each time – that‘s where continuous delivery comes in! In the next section, we‘ll fully automate our build and deployment process.
Automating releases with Azure Pipelines
To complete our continuous delivery workflow, we‘ll setup an Azure Pipelines to automatically build, test, and deploy our Vue app whenever we push new code.
Azure Pipelines is a cloud-based CI/CD service that works with any language, platform, and cloud. It offers a wide range of pre-built tasks and integrations to automate your pipeline.
Creating the pipeline
First, push your Vue app code to an Azure Repos repository (or GitHub, or elsewhere). Then, in your Azure DevOps project, go to "Pipelines" and click "New pipeline".
Select your code repository and choose the "Starter pipeline". This will generate a sample azure-pipelines.yml
file that defines your pipeline steps. Replace the contents with:
trigger:
- main
pool:
vmImage: ubuntu-latest
variables:
dockerRegistryServiceConnection: ‘<registry-connection>‘
imageRepository: ‘my-vue-app‘
containerRegistry: ‘<registry-name>.azurecr.io‘
dockerfilePath: ‘**/Dockerfile‘
tag: ‘$(Build.BuildId)‘
stages:
- stage: Build
displayName: Build and push stage
jobs:
- job: Build
displayName: Build
pool:
vmImage: ubuntu-latest
steps:
- task: Npm@1
displayName: Run npm install
inputs:
command: ‘install‘
- task: Npm@1
displayName: Run npm build
inputs:
command: ‘custom‘
customCommand: ‘run build‘
- task: Docker@2
displayName: Build and push Docker image
inputs:
containerRegistry: ‘$(dockerRegistryServiceConnection)‘
repository: ‘$(imageRepository)‘
command: ‘buildAndPush‘
Dockerfile: ‘**/Dockerfile‘
tags: |
$(tag)
latest
- stage: Deploy
displayName: Deploy to web app
jobs:
- job: Deploy
displayName: Deploy
pool:
vmImage: ubuntu-latest
steps:
- task: AzureWebApp@1
displayName: ‘Deploy Azure Web App‘
inputs:
azureSubscription: ‘<azure-subscription>‘
appName: ‘<app-name>‘
containers: ‘$(containerRegistry)/$(imageRepository):$(tag)‘
This pipeline has two stages:
-
Build stage – Installs dependencies, builds the Vue app, builds the Docker image, and pushes it to ACR. The resulting image is tagged with the build ID and
latest
. -
Deploy stage – Deploys the new Docker image to our Azure web app using the Azure Web App task.
You‘ll need to replace the <placeholders>
with your own values:
-
<registry-connection>
– The name of an Azure service connection for your ACR. Create this under "Project settings" > "Service connections". -
<azure-subscription>
– The name of your Azure service connection. Create this under "Project settings" > "Service connections" and choose the Azure Resource Manager type. -
<registry-name>
– The name of your Azure Container Registry, e.g. "myacr" -
<app-name>
– The name of your Azure Web App
Commit this file to your repo. This will trigger your first pipeline run! You can view the status and logs under the "Pipelines" section of Azure DevOps:
The pipeline will checkout your code, run the build, push the Docker image to ACR, and deploy that image to your web app. If all goes well, you should see your changes live after a few minutes!
Optimizing the pipeline
This basic pipeline automates our entire release process end-to-end which is a huge step forward! But there are a few best practices and optimizations we can add:
-
Caching – Cache the
node_modules
directory between builds using theCache
task to speed up the npm install step. -
Testing – Run your unit and integration tests in the pipeline and block the release if any fail. You can use the
Npm
task with a custom command likenpm run test
. -
Release gates – Add approval gates to control when releases happen. For example, require manual approval for deployments to production.
-
Rolling updates – Implement rolling updates to gradually shift traffic to the new version of your app rather than all at once. The
Azure Web App
task supports this with itsdeploymentMethod
option.
Here‘s an updated azure-pipelines.yml
with some of these enhancements:
variables:
nodeVersion: ‘14.x‘
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: NodeTool@0
inputs:
versionSpec: ‘$(nodeVersion)‘
displayName: ‘Install Node.js‘
- task: Cache@2
inputs:
key: ‘npm | "$(Agent.OS)" | package-lock.json‘
path: ‘node_modules‘
cacheHitVar: ‘CACHE_RESTORED‘
displayName: Cache node_modules
- task: Npm@1
condition: ne(variables.CACHE_RESTORED, ‘true‘)
inputs:
command: ‘ci‘
displayName: ‘Run npm ci if cache not restored‘
- task: Npm@1
inputs:
command: ‘custom‘
customCommand: ‘run build‘
displayName: ‘Run npm run build‘
- task: Npm@1
inputs:
command: ‘custom‘
customCommand: ‘run test‘
displayName: ‘Run npm test‘
- task: Docker@2
inputs:
containerRegistry: ‘$(dockerRegistryServiceConnection)‘
repository: ‘$(imageRepository)‘
command: ‘buildAndPush‘
Dockerfile: ‘**/Dockerfile‘
tags: |
$(tag)
latest
- stage: Deploy
jobs:
- deployment: Production
pool:
vmImage: ‘ubuntu-latest‘
environment: production
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: ‘<azure-subscription>‘
appName: ‘<app-name>‘
containers: ‘$(containerRegistry)/$(imageRepository):$(tag)‘
deploymentMethod: ‘Gradual swap‘
The key changes:
- Caching
node_modules
to speed up npm installs - Adding a
npm test
step to run automated tests - Requiring an approval for the "Production" environment
- Using the "Gradual swap" deployment method for zero-downtime rolling updates
Feel free to further customize your pipeline to fit your team‘s needs. The great thing about defining your pipeline as code is that you can continuously improve and iterate on your release process!
Conclusion
Whew, that was a lot! Let‘s recap what we accomplished:
- Packaged a Vue app in a Docker container
- Pushed the container image to Azure Container Registry
- Deployed the containerized app to Azure Web Apps
- Created a CI/CD pipeline in Azure Pipelines to automate the entire workflow
By leveraging Vue, Docker, and Azure, you can implement a reliable and efficient continuous delivery process for your web applications. Automating your releases enables you to ship quality code faster and more frequently with confidence.
Of course, this is just the tip of the iceberg. True continuous delivery requires a shift in mindset and culture. The goal is to make each step of the software development lifecycle – from planning to coding to testing to deployment – as streamlined and automated as possible.
Some additional continuous delivery best practices include:
- Implementing trunk-based development and feature flags
- Automating your infrastructure provisioning
- Practicing "shift left" testing to catch bugs earlier
- Monitoring your applications proactively with good observability
- Establishing a blameless culture of continuous improvement
Ultimately, continuous delivery is a journey – not a destination. Start small, iterate often, and strive to make your release process a little bit better each day.
I hope this guide gave you a solid foundation to kickstart your continuous delivery transformation! Feel free to reach out if you have any questions. Happy shipping!
References
[1] 2021 State of DevOps Report, Google Cloud[2] Containers in the Enterprise, 2020, Red Hat
[3] 10 Key Benefits of Microsoft Azure, Hanu
[4] Stack Overflow Developer Survey 2021