Monday, May 11, 2020

CI/CD pipelines with ASP.NET MVC 3.1, GitHub Actions and Docker Hub

Overview

In this post I will show you how easy it is to implement a CI/CD DevOps pipeline using GitHub Actions. The sample web application I will use is an ASP.NET Core 3.1 MVC application and I will deploy it to Docker Hub. 
Before proceeding, It is assumed that you have the following pre-requisites: 
  • You already have a Docker Hub account
  • You already have a GitHub account
  • You have .NET Core 3.1 (or later) installed on your computer

Creating a simple ASP.NET Core 3.1 application

In a working directory, execute the following command from within a terminal window:

dotnet new mvc -o GithubActions2DockerHub

Change directory to the newly created app:

cd GithubActions2DockerHub

Add a .gitignore file to the application:

dotnet new gitignore

Let us make the application our own by, perhaps, changing the title of the app and the background color.

1) Add the following CSS to wwwroot/css/site.css:
body {
   background-color: tomato;
}

2) In Views/Home/Index.cshtml, change the main heading from:

<h1 class="display-4">Welcome</h1>

TO

<h1 class="display-4">Welcome to our MVC Core 3.1 App</h1>

Let us now run the application and see what it looks like. In the terminal window run the following command:

dotnet run

Point your browser to https://localhost:5001. You will see a strangely colored web page that looks like this:



Stop the server by hitting CTRL + C in the terminal window.

Dockerfile

In the root folder of your project, add a file named Dockerfile (no extension) with the following content:
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
WORKDIR /app

# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "GithubActions2DockerHub.dll"]

Push your code to GitHub.

We are now ready to push this application to GitGub. Go to GitHub and create a repository. I created a repository named GithubActions2DockerHub. Copy the commands that are shown on the GitHub page, under “…or push an existing repository from the command line”, that looks like this:


Back in your application’s terminal window, type the following commands to initialize a git repo, add files to the repo and commit your changes:

git init
git add .
git commit -m "1st commit"

Next, we will push the code to GitHub. Paste the git commands that you copied from GitHub. This is what it looked like for me:

git remote add origin https://github.com/medhatelmasry/GithubActions2DockerHub.gitgit push -u origin master

If you refresh the GitHub page, you will see that your code is in GitHub. It looks like this:

Click on Secrets on the left-side:

Build & Deploy using GitHub Actions

 Back it GitHub, click on the Settings tab of your application’s repo:
 
Click on Secrets on the left-side:
Click on “Add a new secret”:
 You will enter your Docker Hub username and password as GitHub secrets. Enter secrets DOCKER_USERNAME & DOCKER_PASSWORD together with their respectivev values:
    
Click on Actions on the top navigation bar in GitHub:
You will see a multitude of templates for a multitude of technologies and programming languages. Scroll down to the “Continuous integration workflows” and select “Docker image”.
This will create a .github/Workflows folder in your application. Name the file deploy_to_docker_hub.yml. 
Your deploy_to_docker_hub.yml looks like this:
name: Docker Image CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

  jobs:

    build:

      runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Build the Docker image
        run: docker build . --file Dockerfile --tag my-image-name:$(date +%s)

Unfortunately, the above instructions only build the docker file but do not deploy to docker hub. We will replace the instructions (under steps:) with instructions that checkout, build and deploy.

Therefore delete the following lines at the bottom of the deploy_to_docker_hub.yml file:

- name: Build the Docker image
run: docker build . --file Dockerfile --tag my-image-name:$(date +%s)

Replace the above code with the following:

- uses: docker/build-push-action@v1
  with:
    username: ${{ secrets.DOCKER_USERNAME }}
    password: ${{ secrets.DOCKER_PASSWORD }}
    repository: melmasry/aspnetmvc
    tags: v1

The image will be named melmasry/aspnetmvc and the tag (version) will be v1.
Make sure tab the instructions in deploy_to_docker_hub.yml so that it is indented as shown below:

name: Docker Image CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - uses: docker/build-push-action@v1
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
        repository: melmasry/aspnetmvc
        tags: latest

NOTE: replace melmasry with your Docker Hub username.

Click on the green “Start commit” button:
Enter a comment and description for the commit then click on the “Commit new file” button:
 Click on “Actions” >> “All Workflows” >> your workflow.  
Then click on build:
The build and deploy should be succcessfully completed. The real test comes with pulling the image from DockerHub and running it locally on your computer. Run the following command from a terminal window on your computer:

docker run -d -p 8888:80 melmasry/aspnetmvc:latest

NOTE: replace melmasry with your Docker Hub username.
Point your browser to http://localhost:8888. You will see the same ugly tomato colored web page:
 
Happy coding!!

Monday, April 20, 2020

Working with JSON APIs from ASP.NET MVC

Companion Video: https://youtu.be/r8stP_6V0OY
Source Code: https://github.com/medhatelmasry/ConsumeStudentsAPI

APIs can be consumed from any type of application. In your career you will consume APIs mostly from JavaScript. We can, however, consume APIs from an ASP.NET MVC application too. This is what we will be doing today. There is an online Students API that we will be working with. This API works with the following HTTP methods:
POST
insert
PUT
update
GET
read
DELETE
delete
The Students API has the following columns:
studentId
string
firstName
string
lastName
string
school
string
Let us first create an ASP.NET Core MVC application. Go into your working directory in a terminal window and execute this command:
dotnet new mvc -o ConsumeStudentsAPI
The above command will create an ASP.NET MVC web application in a directory called ConsumeStudentsAPI. Next, also in a terminal window, change directory with:
cd ConsumeStudentsAPI
We will need to use a package named Newtonsoft.Json. Therefore, execute the following command in a terminal window to add this package:
dotnet add package Newtonsoft.Json
You can continue either with Visual Studio 2019 or Visual Studio Code. It is really up to you.
Add to the Models folder a class file named Student.cs with the following class definition:
public class Student {

  [Display(Name = "ID")]
  [Key]
  public string studentId { get; set; }

  [Required]
  [Display(Name = "First Name")]
  public string firstName { get; set; }
 
  [Required]
  [Display(Name = "Last Name")]
  public string lastName { get; set; }

  [Required]
  [Display(Name = "School")]
  public string school { get; set; }
}
Notice these annotations:
·       Display allows you to have an alternative display name for a property in the model
·       Key sets studentId as the primary key
·       Required makes sure that the user enters a value for this property.
We will be using the IHttpClientFactory factory class to make HTTP requests to the API. Therefore, we need to add a singleton object into the application. Add this code to the ConfigureServices() method in Startup.cs:
services.AddHttpClient();
Next, add to the Controllers folder a file named StudentsController.cs with the following class definitions:
public class StudentsController : Controller {
  const string BASE_URL = "https://api.azurewebsites.net/";
  private readonly ILogger<StudentsController> _logger;
  private readonly IHttpClientFactory _clientFactory;
  public IEnumerable<Student> Students { get; set; }
  public bool GetStudentsError { get; private set; }
 
  public StudentsController(ILogger<StudentsController> logger, IHttpClientFactory clientFactory) {
    _logger = logger;
    _clientFactory = clientFactory;
  }

  public async Task<IActionResult> Index() {
    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Get;
    message.RequestUri = new Uri($"{BASE_URL}api/students");
    message.Headers.Add("Accept", "application/json");
 
    var client = _clientFactory.CreateClient();

    var response = await client.SendAsync(message);

    if (response.IsSuccessStatusCode) {
        using var responseStream = await response.Content.ReadAsStreamAsync();
        Students = await JsonSerializer.DeserializeAsync<IEnumerable<Student>>(responseStream);
    } else {
        GetStudentsError = true;
        Students = Array.Empty<Student>();
    }

    return View(Students);
  }

  public async Task<IActionResult> Details(string id) {
    if (id == null)
      return NotFound();

    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Get;
    message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");
    message.Headers.Add("Accept", "application/json");

    var client = _clientFactory.CreateClient();

    var response = await client.SendAsync(message);

    Student student = null;

    if (response.IsSuccessStatusCode) {
      using var responseStream = await response.Content.ReadAsStreamAsync();
      student = await JsonSerializer.DeserializeAsync<Student>(responseStream);
    } else {
      GetStudentsError = true;
    }

    if (student == null)
      return NotFound();

    return View(student);

  }

  public IActionResult Create() {
    return View();
  }

  [HttpPost]
  [ValidateAntiForgeryToken]
  public async Task<IActionResult> Create([Bind("studentId,firstName,lastName,school")] Student student)
  {
    if (ModelState.IsValid) {
      HttpContent httpContent = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(student), Encoding.UTF8);
      httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

      var message = new HttpRequestMessage();
      message.Content = httpContent;
      message.Method = HttpMethod.Post;
      message.RequestUri = new Uri($"{BASE_URL}api/students");

      HttpClient client = _clientFactory.CreateClient();
      HttpResponseMessage response = await client.SendAsync(message);

      var result = await response.Content.ReadAsStringAsync();

      return RedirectToAction(nameof(Index));
    }

    return View(student);
  }

  public async Task<IActionResult> Edit(string id) {
    if (id == null)
      return NotFound();

    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Get;
    message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");
    message.Headers.Add("Accept", "application/json");

    var client = _clientFactory.CreateClient();

    var response = await client.SendAsync(message);

    Student student = null;

    if (response.IsSuccessStatusCode) {
      using var responseStream = await response.Content.ReadAsStreamAsync();
      student = await JsonSerializer.DeserializeAsync<Student>(responseStream);
    } else {
      GetStudentsError = true;
    }

    if (student == null)
      return NotFound();

    return View(student);

  }

  [HttpPost]
  [ValidateAntiForgeryToken]
  public async Task<IActionResult> Edit(string id, [Bind("studentId,firstName,lastName,school")] Student student)
  {
    if (id != student.studentId)
      return NotFound();

    if (ModelState.IsValid) {
      HttpContent httpContent = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(student), Encoding.UTF8);
      httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

      var message = new HttpRequestMessage();
      message.Content = httpContent;
      message.Method = HttpMethod.Put;
      message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");

      HttpClient client = _clientFactory.CreateClient();
      HttpResponseMessage response = await client.SendAsync(message);

      var result = await response.Content.ReadAsStringAsync();

      return RedirectToAction(nameof(Index));
    }

    return View(student);
  }

  public async Task<IActionResult> Delete(string id) {
    if (id == null)
      return NotFound();

    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Get;
    message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");
    message.Headers.Add("Accept", "application/json");

    var client = _clientFactory.CreateClient();

    var response = await client.SendAsync(message);

    Student student = null;

    if (response.IsSuccessStatusCode) {
      using var responseStream = await response.Content.ReadAsStreamAsync();
      student = await JsonSerializer.DeserializeAsync<Student>(responseStream);
    } else {
        GetStudentsError = true;
    }

    if (student == null)
      return NotFound();

    return View(student);

  }

  [HttpPost, ActionName("Delete")]
  [ValidateAntiForgeryToken]
  public async Task<IActionResult> DeleteConfirmed(string id) {
    var message = new HttpRequestMessage();
    message.Method = HttpMethod.Delete;
    message.RequestUri = new Uri($"{BASE_URL}api/students/{id}");

    HttpClient client = _clientFactory.CreateClient();
    HttpResponseMessage response = await client.SendAsync(message);

    var result = await response.Content.ReadAsStringAsync();

    return RedirectToAction(nameof(Index));
  }
}
The above code represents a controller that has action methods to list, add, edit and delete data. We will need to have views for the action methods in StudentsContrller. Therefore, in the Views folder, create another folder named Students. Inside of the Views/Students folder add these Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml and Index.cshtml files:

Create.cshtml

@model ConsumeStudentsAPI.Models.Student

@{
  ViewData["Title"] = "Add Student";
}

<h1>@ViewData["Title"]</h1>

<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Create">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>

      <div class="form-group">
        <label asp-for="studentId" class="control-label"></label>
        <input asp-for="studentId" class="form-control" />
        <span asp-validation-for="studentId" class="text-danger"></span>
      </div>

      <div class="form-group">
        <label asp-for="firstName" class="control-label"></label>
        <input asp-for="firstName" class="form-control" />
        <span asp-validation-for="firstName" class="text-danger"></span>
      </div>

      <div class="form-group">
        <label asp-for="lastName" class="control-label"></label>
        <input asp-for="lastName" class="form-control" />
        <span asp-validation-for="lastName" class="text-danger"></span>
      </div>

      <div class="form-group">
        <label asp-for="school" class="control-label"></label>
        <input asp-for="school" class="form-control" />
        <span asp-validation-for="school" class="text-danger"></span>
      </div>

      <input type="submit" value="Create" class="btn btn-success" />
      <a asp-action="Index" class="btn btn-primary">&lt;&lt; Back to List</a>
    </form>
  </div>
</div>

Delete.cshtml

@model ConsumeStudentsAPI.Models.Student
@{
    ViewData["Title"] = "Delete Student";
}
 
<h1>@ViewData["Title"]</h1>
<h3>Are you sure you want to delete this?</h3>
<div>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.studentId)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.studentId)
        </dd> 
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.firstName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.firstName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.lastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.lastName)
        </dd>
 
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.school)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.school)
        </dd>
    </dl>   
    <form asp-action="Delete">
        <input type="hidden" asp-for="studentId" />
        <input type="submit" value="Delete" class="btn btn-danger" /> 
        <a asp-action="Index" class="btn btn-primary">&lt;&lt; Back to List</a>
    </form>
</div>

Details.cshtml

@model ConsumeStudentsAPI.Models.Student

@{
  ViewData["Title"] = "Student Details";
}

<h1>@ViewData["Title"]</h1>
<div>
  <hr />
  <dl class="row">
    <dt class="col-sm-2">
        @Html.DisplayNameFor(model => model.studentId)
    </dt>
    <dd class="col-sm-10">
      @Html.DisplayFor(model => model.studentId)
    </dd>

    <dt class="col-sm-2">
      @Html.DisplayNameFor(model => model.firstName)
    </dt>
    <dd class="col-sm-10">
      @Html.DisplayFor(model => model.firstName)
    </dd>

    <dt class="col-sm-2">
      @Html.DisplayNameFor(model => model.lastName)
    </dt>
    <dd class="col-sm-10">
      @Html.DisplayFor(model => model.lastName)
    </dd>

    <dt class="col-sm-2">
          @Html.DisplayNameFor(model => model.school)
    </dt>
    <dd class="col-sm-10">
      @Html.DisplayFor(model => model.school)
    </dd>
  </dl>
</div>
<div>
  <a asp-action="Edit" asp-route-id="@Model.studentId" class="btn btn-warning">Edit</a> 
  <a asp-action="Index" class="btn btn-primary">&lt;&lt; Back to List</a>
</div>


Edit.cshtml



@model ConsumeStudentsAPI.Models.Student

@{
  ViewData["Title"] = "Edit Student";
}

<h1>@ViewData["Title"]</h1>
<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Edit">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>
      <input type="hidden" asp-for="studentId" />
      <div class="form-group">
        <label asp-for="firstName" class="control-label"></label>
        <input asp-for="firstName" class="form-control" />
        <span asp-validation-for="firstName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="lastName" class="control-label"></label>
        <input asp-for="lastName" class="form-control" />
        <span asp-validation-for="lastName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="school" class="control-label"></label>
        <input asp-for="school" class="form-control" />
        <span asp-validation-for="school" class="text-danger"></span>
      </div>            
      <div class="form-group">
        <input type="submit" value="Save" class="btn btn-warning" />
        <a asp-action="Index" class="btn btn-primary">&lt;&lt; Back to List</a>
      </div>
    </form>
  </div>
</div>

Index.cshtml

@model IEnumerable<ConsumeStudentsAPI.Models.Student>

@{
  ViewData["Title"] = "List Students";
}

<div>
  <h1 class="display-4">@ViewData["Title"]</h1>

  <p>
      <a asp-action="Create" class="btn btn-sm btn-success">Create New</a>
  </p>

  <table class="table table-striped table-bordered">
    <tr>
      <th>@Html.DisplayNameFor(model => model.studentId)</th>
      <th>@Html.DisplayNameFor(model => model.firstName)</th>
      <th>@Html.DisplayNameFor(model => model.lastName)</th>
      <th>@Html.DisplayNameFor(model => model.school)</th>
      <th></th>
    </tr>
    @foreach (var item in Model)
    {
      <tr>
        <td>@item.studentId</td>
        <td>@item.firstName</td>
        <td>@item.lastName</td>
        <td>@item.school</td>
        <td style="text-align: center;">
          <a asp-action="Edit" asp-route-id="@item.studentId" class="btn btn-sm btn-warning">Edit</a>
          <a asp-action="Details" asp-route-id="@item.studentId" class="btn btn-sm btn-info">Details</a>
          <a asp-action="Delete" asp-route-id="@item.studentId" class="btn btn-sm btn-danger">Delete</a>
        </td>
      </tr>
      }
  </table>
</div>
We need to add the Students link to the main menu. Therefore add this <li> tag to Views/Shared/_Layout.cshtml after around line 26:
<li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a> </li>
Let us run the application and see what we have. The home page looks like this:
Click on Students. A list of students in the database will be shown:
You can try adding, editing, displaying and deleting data.






Wednesday, March 25, 2020

Implementing CI/CD DevOps pipelines using a simple ASP.NET Core 3.1 MVC application with GitHub Actions and Azure App Services


In this post I will show you how easy it is to implement a CI/CD DevOps pipeline using GitHb Actions. The sample web application I will use is an ASP.NET Core 3.1 MVC application and I will deploy it to Azure App Services. 

Before proceeding, It is assumed that you have the following pre-requisites:
- You already have an Azure Account
- You already have a GitHub account
- You have .NET Core 3.1 (or later) installed on your computer
Creating a simple ASP.NET Core 3.1 application
In a working directory, execute the following command from within a terminal window:

dotnet new mvc -o GithubActions2Azure

Change directory to the newly created app:

cd GithubActions2Azure

Add a .gitignore file to the application:

dotnet new gitignore

Let us make the application our own by, perhaps, changing the title of the app and the background color.

1) Add the following CSS to wwwroot/css/site.css


body {   background-color: tomato;}


2) In Views/Home/Index.cshtml, change the main heading from:

<h1 class="display-4">Welcome</h1>

           TO

<h1 class="display-4">Welcome to our MVC Core 3.1 App</h1>

Let us now run the application and see what it looks like. In the terminal window run the following command:

dotnet run

Point your browser to https://localhost:5001. You will see a strangely colored web page that looks like this:


Stop the server by hitting CTRL + C in the terminal window.

Push code to GitHub

We are now ready to push this application to GitGub. Go to GitHub and create a repository. I created a repository named GithubActions2Azure. Copy the commands that are shown on the GitHub page that looks like this:

Back in your application’s terminal window, type the following commands to initialize a git repo, add files to the repo and commit the changes:

git init
git add .
git commit -m "1st commit"

Next, we will push the code to GitHub. Paste the git commands that you copied from GitHub. This is what it looked like for me:

git remote add origin https://github.com/medhatelmasry/GithubActions2Azure.git
git push -u origin master

If you refresh the GitHub page, you will see that the code is in GitHub. It looks like this:

























Azure

The next step is to create a web app in Azure. Login to the Azure portal at https://portal.azure.com/. Click on Create a resource:

Enter “web" in the filter then choose "Web App”:


On the next page, click on the blue Create button:

 

Choose a subscription then create a new Resource Group:


In named my new resource group GithubActions2Azure. Here are all the choices I made:


Click on Create on the next page:


Creation of a new Azure web service takes less than three minutes. When it is completed, a message appears declaring that “Your deployment is complete”. Click on the blue “Go to resource” button:


We need the publish profile. Click on “Get publish profile” to download your publish profile XML file:


Build & Deploy using GitHub Actions
 Back it GitHub, click on the Settings tab of your application’s repo:


Click on Secrets on the left-side:


Click on “Add a new secret”:


Give the secret the name AZURE_WEBAPP_PUBLISH_PROFILE and paste the contents of the publish profile file that you earlier downloaded from the Azure portal for your web app:

 
Click on Actions on the top navigation bar in GitHub:


You will see a multitude of templates for a multitude of technologies and programming languages. We will paste our own template, therefore, click on the “Set up a workflow yourself” link on the right-side:


This will create a .github/Workflows folder in your application. Name the file deploy_to_azure.yml then click on the green “Start commit” button:


Enter a comment and description for the commit then click on the “Commit new file” button:


Go to this site to get an appropriate template for our web app: https://github.com/Azure/webapps-deploy. Scroll down until you find this section then click on dotnet.yml:


Copy the contents of the asp.net-core-webapp-on-azure.yml. Return to the GitHub repo of our application, edit the deploy_to_azure.yml file, and replace the contents by pasting the clipboard. This is what it looks like for me:


On line 18, set the value of AZURE_WEBAPP_NAME to the name of your app. In my case it is GithubActions2Azure.

On line 20, set the value of DOTNET_VERSION with the exact version of .NET Core with which you created your app. You can find out by running the following command in a terminal window:

dotnet --version

In my case, the .NET Core version is 3.1.200.

Click on the green “Start commit” button, give it a title then click on “Commit changes”.

Click on Actions on the top navigation bar:


Click on “Deploy ASP.NET Are all to ..”. If all goes well, there should be a green check mark beside the workflow, which was responsible for building and deploying the app to Azure. Click on “Updated deploy_to_azure.yml”.


Click on “build-and-deploy” on the left-side:


You will be comforted by the fact that all tasks completed successfully:


To make sure all is well, go to the Azure portal and click on the URL of your website:


Our ugly looking tomato-colored website appears:


Note that if you make any changes to your source code and push it to GitHub, it will automatically get deployed to Azure. This is what CI/CD is all about. Let's try that.

CI/CD
In a terminal window at your project folder, do a pull in order to get all the changes we made in GitHub when we added the .yml file:

git pull

In wwwroot/css/site.css, change the background-color to wheat. Let us push our changes to Github with these commands:

git add .
git commit -m "changed background color to wheat"
git push origin master

Back in GitHub, you will find that the deployment is running:


Upon completion it will display a green check mark:

Refresh the website in your browser and you will find that it has changed background color to something nicer:


Thanks for coming this far in my tutorial and I hope you found it useful.