<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ GitLab - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ GitLab - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 23 May 2026 19:41:43 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/gitlab/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Automate Machine Learning Model Publishing with the Gitlab Package Registry ]]>
                </title>
                <description>
                    <![CDATA[ By Yacine Mahdid In this tutorial we'll learn how to automatically publish machine learning models in a Gitlab package registry and make them available for your teammates to use. You can also use this technique to share a packaged version of your cod... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/ml-model-publishing-with-gitlab-package-registry/</link>
                <guid isPermaLink="false">66d4617636c45a88f96b7d11</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CI/CD ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitLab ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ neural networks ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 15 Apr 2021 16:33:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/04/photo-1510380290144-9e40d2438af5.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Yacine Mahdid</p>
<p>In this tutorial we'll learn how to automatically publish machine learning models in a Gitlab package registry and make them available for your teammates to use. You can also use this technique to share a packaged version of your code as a binary.</p>
<p>If you are a beginner Gitlab user and are unfamiliar with CI/CD techniques, this tutorial is for you! A basic understanding of how machine-learning and deep learning is a plus, but it isn't a requirement to understand the CI/CD publishing part.</p>
<h3 id="heading-heres-what-well-cover">Here's what we'll cover:</h3>
<ul>
<li>Gitlab Code Setup</li>
<li>Deep Convolutional Neural Network Code</li>
<li>Image Recognition Code</li>
<li>Branching Methodology</li>
<li>CI/CD Uploading</li>
<li>Conclusion</li>
</ul>
<h2 id="heading-first-some-background">First, Some Background</h2>
<p>At some point during your machine learning engineer career you might need to share a model you've trained with other developers. There are multiple ways of doing this.</p>
<h3 id="heading-give-access-to-the-repository">Give access to the repository</h3>
<p>If you don't mind showing your whole code, this is a very viable option. </p>
<p>If you use a good branching methodology your colleagues will only need to look at the main branch in order to figure out what's the most up to date model they can use. Then they can check the README.md to learn how to use it. </p>
<p>However, giving full access to the repository might not be a viable option for you.</p>
<h3 id="heading-share-the-latest-model-manually">Share the latest model manually</h3>
<p>Another way would be to extract the relevant code that you want to make public and send it to them manually. </p>
<p>This can become a bit of a mess if you are working with more than one person because the model you send might not be up to date. It also puts the burden on you to make sure that people are always using the latest version of your model. </p>
<h3 id="heading-share-the-latest-model-automatically">Share the latest model automatically</h3>
<p>A simpler solution, even in the case where the repository code is available, is to put the packaging burden on a CI/CD pipeline. </p>
<p>This is the topic of this tutorial, and our setup will look like this:</p>
<ul>
<li>The code repository, CI/CD tool set, and package registry will be on Gitlab</li>
<li>The code we'll be packaging will be a simple trained PyTorch neural network on the MNIST dataset for digit recognition.</li>
<li>All the instructions and the requirements will be available in the package.</li>
</ul>
<p>🚨 <strong>Disclaimer</strong> 🚨: This isn't how you should deploy a PyTorch production-ready model! To learn how to do this, check out this tutorial on <a target="_blank" href="https://pytorch.org/tutorials/advanced/cpp_export.html">TorchScript</a>.</p>
<p>Let's get started.</p>
<h2 id="heading-gitlab-code-setup">Gitlab Code Setup</h2>
<p>For this tutorial we will bundle four files:</p>
<ul>
<li><strong>model.pth</strong>: which is a pickled version of the latest version of the trained model.</li>
<li><strong>run_mnist.py</strong>: simple Python script to run the model to detect a digit from a png image.</li>
<li><strong>requirements.txt</strong>: text file containing all the dependencies required to run the model.</li>
<li><strong>INSTRUCTION.md</strong>: step by step instructions to use the package.</li>
</ul>
<p>The package can then be used freely by anyone who has access to the package registry and will be automatically updated.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/package.png" alt="Image" width="600" height="400" loading="lazy">
<em>The package will then look like this on Gitlab Package Registry!</em></p>
<p>Let's jump into the neural network code, which is a modified version of this <a target="_blank" href="https://nextjournal.com/gkoehler/pytorch-mnist">comprehensive article on digit recognition</a>. The modified code can be found over at <a target="_blank" href="https://gitlab.com/yacineg4/example-ml-packaging-pipeline">my public Gitlab repository</a>.</p>
<h2 id="heading-deep-convolutional-neural-network-code">Deep Convolutional Neural Network Code</h2>
<p>In the section below, you will see quite a lot of terminology about deep neural networks. This isn't a tutorial on neural networks, so if you feel a bit overwhelmed by the specifics you can jump directly to the <strong>Branching Methodology</strong> section. </p>
<p>Just keep in mind that we've trained some sort of image recognition program that, given a <code>.png</code> file representing a digit, will be able to tell you what number it contains.</p>
<p>However, for those that want to get a better understanding about how Deep Neural Networks work under the hood, you can take a look at <a target="_blank" href="https://youtu.be/b_w4eEiogaE">my tutorial where I build one from scratch</a> or checkout the <a target="_blank" href="https://github.com/yacineMahdid/artificial-intelligence-and-machine-learning">code directly in my Github</a>.</p>
<h3 id="heading-neural-network-definition">Neural Network Definition</h3>
<p>The network definition code is very straightforward since the network we will use is simple. It has the following characteristics:</p>
<ul>
<li>2 convolutional layers.</li>
<li><a target="_blank" href="https://machinelearningmastery.com/dropout-for-regularizing-deep-neural-networks/">Dropout</a> is applied on the second convolutional layer.</li>
<li><a target="_blank" href="https://machinelearningmastery.com/rectified-linear-activation-function-for-deep-learning-neural-networks/">Relu</a> activation functions applied on all neurons.</li>
<li>2 fully connected layers at the end for inference.</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> torch
<span class="hljs-keyword">import</span> torchvision

<span class="hljs-keyword">import</span> torch.nn <span class="hljs-keyword">as</span> nn
<span class="hljs-keyword">import</span> torch.nn.functional <span class="hljs-keyword">as</span> F
<span class="hljs-keyword">import</span> torch.optim <span class="hljs-keyword">as</span> optim


<span class="hljs-comment"># Define the network</span>
<span class="hljs-comment"># It's a 2 convolutional layer with dropout at the 2nd and finally 2 fully connected layer</span>
<span class="hljs-comment"># All layers use relu</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Net</span>(<span class="hljs-params">nn.Module</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(<span class="hljs-number">1</span>, <span class="hljs-number">10</span>, kernel_size=<span class="hljs-number">5</span>)
        self.conv2 = nn.Conv2d(<span class="hljs-number">10</span>, <span class="hljs-number">20</span>, kernel_size=<span class="hljs-number">5</span>)
        self.conv2_drop = nn.Dropout2d()
        self.fc1 = nn.Linear(<span class="hljs-number">320</span>, <span class="hljs-number">50</span>)
        self.fc2 = nn.Linear(<span class="hljs-number">50</span>, <span class="hljs-number">10</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span>(<span class="hljs-params">self, x</span>):</span>
        x = F.relu(F.max_pool2d(self.conv1(x), <span class="hljs-number">2</span>))
        x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), <span class="hljs-number">2</span>))
        x = x.view(<span class="hljs-number">-1</span>, <span class="hljs-number">320</span>)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        <span class="hljs-keyword">return</span> F.log_softmax(x, dim=<span class="hljs-number">1</span>)
</code></pre>
<h3 id="heading-training-function">Training Function</h3>
<p>We then created a utility training function in order to iteratively improve our defined network using gradient descent. If you want to learn more about how gradient descent works check out <a target="_blank" href="https://youtu.be/IH9kqpMORLM">my short tutorial on it</a>.</p>
<p>This training regimen will do the following:</p>
<ul>
<li>Iterate on batches of training data representing 28 by 28 digits.</li>
<li>Use the <a target="_blank" href="https://medium.com/deeplearningmadeeasy/negative-log-likelihood-6bd79b55d8b6">negative log likelihood cost function</a> to calculate the loss.</li>
<li>Calculate gradients.</li>
<li>Optimize the weights of the network using gradient descent.</li>
<li>Save the model at fixed intervals.</li>
</ul>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">train</span>(<span class="hljs-params">network, optimizer, train_loader, epoch_id, log_interval=<span class="hljs-number">10</span></span>):</span>
  <span class="hljs-string">"""Run the training regiment on the training set using train_loader

    Args:
        network: The instantiated network.
        optimizer: The optimizer used to change the weights.
        train_loader: the loader for the training set already setup
        epoch_id: the current id of the epoch used for cosmetic reason.
        log_interval: interval at which we print an output

    Returns:
        nothing, will save directly at root level the model and the optimizer state

  """</span>

  <span class="hljs-comment"># Set the network in training mode</span>
  network.train()

  <span class="hljs-comment"># Iterate over the full training set</span>
  <span class="hljs-keyword">for</span> batch_idx, (data, target) <span class="hljs-keyword">in</span> enumerate(train_loader):

    <span class="hljs-comment"># Calculate the gradients for this batch of data</span>
    optimizer.zero_grad()
    output = network(data)
    loss = F.nll_loss(output, target)
    loss.backward()

    <span class="hljs-comment"># Optimize the network</span>
    optimizer.step()

    <span class="hljs-comment"># Log and save every selected interval</span>
    <span class="hljs-keyword">if</span> batch_idx % log_interval == <span class="hljs-number">0</span>:

      print(<span class="hljs-string">'Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'</span>.format(
        epoch_id, batch_idx * len(data), len(train_loader.dataset),
        <span class="hljs-number">100.</span> * batch_idx / len(train_loader), loss.item()))

      <span class="hljs-comment"># This will save the state as a pickled object</span>
      torch.save(network.state_dict(), <span class="hljs-string">'./model.pth'</span>)
      torch.save(optimizer.state_dict(), <span class="hljs-string">'./optimizer.pth'</span>)
</code></pre>
<p>The data for training can be found over here on the <a target="_blank" href="http://yann.lecun.com/exdb/mnist/">Yan LeCun website</a>. Here we are using the datasets formatted as 28 by 28 PyTorch tensors for training.</p>
<h3 id="heading-testing-function">Testing Function</h3>
<p>The next function we create is a testing function to validate if our network has learned something without reusing the same training data. This function is simple in the sense that it will just tally the correct and incorrect predictions.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test</span>(<span class="hljs-params">network, test_loader</span>):</span>
  <span class="hljs-string">"""Run the testing regiment on the test set using test_loader

    Args:
        network: The instantiated and trained network.
        test_loader: the loader for the testing set already setup

    Returns:
        nothing, will only print result

  """</span>

  <span class="hljs-comment"># Variable instantiation</span>
  test_loss = <span class="hljs-number">0</span>
  correct = <span class="hljs-number">0</span>

  <span class="hljs-comment"># Move the network to evaluate mode instead of training</span>
  network.eval()

  <span class="hljs-comment"># setup torch so to not track any  gradient</span>
  <span class="hljs-keyword">with</span> torch.no_grad():

    <span class="hljs-comment"># Iterate on all the test data and accumulate the loss</span>
    <span class="hljs-keyword">for</span> data, target <span class="hljs-keyword">in</span> test_loader:
      output = network(data)
      test_loss += F.nll_loss(output, target, size_average=<span class="hljs-literal">False</span>).item()
      pred = output.data.max(<span class="hljs-number">1</span>, keepdim=<span class="hljs-literal">True</span>)[<span class="hljs-number">1</span>]
      correct += pred.eq(target.data.view_as(pred)).sum()

  <span class="hljs-comment"># Average loss calculation and printing   </span>
  test_loss /= len(test_loader.dataset)
  print(<span class="hljs-string">'\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'</span>.format(
    test_loss, correct, len(test_loader.dataset),
    <span class="hljs-number">100.</span> * correct / len(test_loader.dataset)))
</code></pre>
<p>This function will be useful to check how well our network has learned after each training iteration.</p>
<h3 id="heading-training-regimen">Training Regimen</h3>
<p>Finally, we can tie all of the above together with the main body of the training script! A few things are happening, but the most important points are the following:</p>
<ul>
<li>We set our hyper parameters statically. A better way to define them would be to use a validation set to figure them out based on the data.</li>
<li>We create our data loader which will ingest data and spit out tensors in the right shape for the network. These loader will transform the data by normalizing them with the global mean and standard deviation for the MNIST datasets.</li>
<li>We use <a target="_blank" href="https://youtu.be/7EuiXb6hFAM">stochastic gradient descent with momentum</a> as the optimization method, which is one of the many flavors of gradient descent we can use.</li>
<li>We loop through the full training dataset's "epoch", the amount of time to train the network while testing on the held-out test datasets.</li>
</ul>
<pre><code class="lang-python"><span class="hljs-comment"># Experimental Parameters that we can tweak</span>
n_epochs = <span class="hljs-number">3</span>
batch_size_train = <span class="hljs-number">64</span>
batch_size_test = <span class="hljs-number">1000</span>
learning_rate = <span class="hljs-number">0.01</span>
momentum = <span class="hljs-number">0.5</span>

<span class="hljs-comment"># Variable from the dataset that should stay as is</span>
global_mean_mnist = <span class="hljs-number">0.1307</span>
global_std_mnist = <span class="hljs-number">0.3081</span>


<span class="hljs-comment"># Random Seed for Reproducible Experimentation</span>
random_seed = <span class="hljs-number">42</span>
torch.backends.cudnn.enabled = <span class="hljs-literal">False</span>
torch.manual_seed(random_seed)


<span class="hljs-comment"># Data Loader to gather the data and then normalize them</span>
train_loader = torch.utils.data.DataLoader(
  torchvision.datasets.MNIST(<span class="hljs-string">'./data/'</span>, train=<span class="hljs-literal">True</span>, download=<span class="hljs-literal">True</span>,
                             transform=torchvision.transforms.Compose([
                               torchvision.transforms.ToTensor(),
                               torchvision.transforms.Normalize(
                                 (global_mean_mnist,), (global_std_mnist,))
                             ])),
  batch_size=batch_size_train, shuffle=<span class="hljs-literal">True</span>)

test_loader = torch.utils.data.DataLoader(
  torchvision.datasets.MNIST(<span class="hljs-string">'./data/'</span>, train=<span class="hljs-literal">False</span>, download=<span class="hljs-literal">True</span>,
                             transform=torchvision.transforms.Compose([
                               torchvision.transforms.ToTensor(),
                               torchvision.transforms.Normalize(
                                 (global_mean_mnist,), (global_std_mnist,))
                             ])),
  batch_size=batch_size_test, shuffle=<span class="hljs-literal">True</span>)

<span class="hljs-comment"># Initialize network and optimizer</span>
network = Net()
optimizer = optim.SGD(network.parameters(), lr=learning_rate,
                      momentum=momentum)

<span class="hljs-comment"># Test first to show that the model didn't learn a thing</span>
test(network, test_loader)

<span class="hljs-comment"># Train on the whole dataset multiple time and test</span>
<span class="hljs-keyword">for</span> epoch_id <span class="hljs-keyword">in</span> range(<span class="hljs-number">1</span>, n_epochs + <span class="hljs-number">1</span>):
  train(network, optimizer, train_loader, epoch_id)
  test(network, test_loader)
</code></pre>
<p>Note that it's very important to test your network on a held-out set to avoid over-fitting on the training data.</p>
<p>All of the above scripts can be found in the file <a target="_blank" href="https://gitlab.com/yacineg4/example-ml-packaging-pipeline/-/blob/master/train_mnist.py">train_mnist.py in the repository</a>. </p>
<p>At this point, we can train a model and have it saved at regular intervals in a pickle format.</p>
<p>We can now use that saved trained mode to evaluate a digit in a <code>.png</code> file.</p>
<h2 id="heading-image-recognition-code">Image Recognition Code</h2>
<p>Let's say we have as an input the following image:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/test_image_0.png" alt="Image" width="600" height="400" loading="lazy">
<em>a small 0 digit</em></p>
<p>or this one:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/test_image_7.png" alt="Image" width="600" height="400" loading="lazy">
<em>a bigger 7 digit</em></p>
<p>How can we make our network, which works on a 28 by 28 PyTorch tensor, evaluate the numbers?</p>
<p>It's fairly straightforward if we follow roughly the same process that the training datasets went through, which is:</p>
<ul>
<li>Have grayscale images (no color or alpha channels)</li>
<li>Resize the images to be 28 by 28 pixels</li>
<li>Normalize the images using the mean and standard deviation of the MNIST datasets.</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:

    <span class="hljs-comment"># Variable iniatilization</span>
    global_mean_mnist = <span class="hljs-number">0.1307</span>
    global_std_mnist = <span class="hljs-number">0.3081</span>

    <span class="hljs-comment"># Loading of the network with right weight</span>
    result_path = <span class="hljs-string">'./model.pth'</span>
    model = Net()
    model.load_state_dict(torch.load(result_path))
    model.eval()

    <span class="hljs-comment"># Setup the transform from image to normalized tensors</span>
    transform = transforms.Compose([
                        transforms.Resize((<span class="hljs-number">28</span>,<span class="hljs-number">28</span>)),
                        transforms.ToTensor(),
                        transforms.Normalize(
                            (global_mean_mnist,), (global_std_mnist,))
                        ])

    <span class="hljs-comment"># Parse the input from the user which should be a filename with the --image flag</span>
    parser = OptionParser()
    parser.add_option(<span class="hljs-string">"--image"</span>, dest = <span class="hljs-string">"input_image_path"</span>,
                      help = <span class="hljs-string">"Input Image Path"</span>)
    (options, args) = parser.parse_args()

    <span class="hljs-comment"># Get the path to the image to decode</span>
    input_image_path = str(options.input_image_path)

    <span class="hljs-comment"># Open the image(s) and do the inference</span>
    images=glob.glob(input_image_path)
    <span class="hljs-keyword">for</span> image <span class="hljs-keyword">in</span> images:

        <span class="hljs-comment"># Convert the image to grayscale</span>
        img = Image.open(image).convert(<span class="hljs-string">'L'</span>)

        <span class="hljs-comment"># Transform the image to a normalized tensor</span>
        img_tensor = transform(img).unsqueeze(<span class="hljs-number">0</span>)

        <span class="hljs-comment"># Make and print the prediction</span>
        output = model(img_tensor).data.max(<span class="hljs-number">1</span>, keepdim=<span class="hljs-literal">True</span>)[<span class="hljs-number">1</span>][<span class="hljs-number">0</span>][<span class="hljs-number">0</span>]
        print(<span class="hljs-string">f"Image is a <span class="hljs-subst">{int(output)}</span>"</span>)
</code></pre>
<p>As you can see, we use a parser to accept an image path on the command line before applying our transformations. Once they are applied we can feed that to our loaded model and collect the output prediction.</p>
<p>⚠️ Don't forget to include the definition of the network in the script (by importing or copy pasting), otherwise the pickled model will not be able to load properly.</p>
<p>We can now run our code like this:</p>
<pre><code class="lang-bash">python run_mnist.py --image NAME_OF_IMAGE.png
</code></pre>
<p>This will simply print the model's inference about what that particular image contains.</p>
<p>Now that we have the basic training and evaluation code set up, let's discuss a bit more about how to use git branching to our advantage to publish this model to the package registry.</p>
<h2 id="heading-branching-methodology">Branching Methodology</h2>
<p>If you are working alone on a project, it is very tempting to simply commit to master/main and be done with it. However, this way of working is very difficult to maintain and it makes incorporating proper CI/CD tools a pain. </p>
<p>A main / develop branch strategy as shown below is more maintainable:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/image-122.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image from: https://nvie.com/posts/a-successful-git-branching-model/</em></p>
<p>By always keeping the main branch clean, we can easily flag our CI/CD pipeline to be triggered as soon as we push to the main. We will be also free to commit as much as we need in the develop branch while we improve our models. </p>
<p>When we are ready for a new deploy we will only need to merge with the main branch (or better yet do a merge-request / pull-request and then merge). </p>
<p>This merge to main should trigger Gitlab to upload the new version of our model to the package registry.</p>
<p>Let's take a look at the simple way to automate publishing to the package registry using the <code>.gitlab-ci.yml</code> file.</p>
<h2 id="heading-cicd-pipeline">CI/CD Pipeline</h2>
<p>The <code>.gitlab-ci.yml</code> file is a special file in your repository used by Gitlab to define what the Gitlab server should do when you push to a repository.</p>
<p>To learn more about how CI/CD works in Gitlab, head over to this <a target="_blank" href="https://medium.com/faun/gitlab-ci-cd-crash-course-6e7bcf696940">Gitlab CI/CD crash course</a>.</p>
<p>In this tutorial our <code>.gitlab-ci.yml</code> file looks like this:</p>
<pre><code class="lang-yml"><span class="hljs-attr">image:</span> <span class="hljs-string">pytorch/pytorch</span>

<span class="hljs-attr">variables:</span>
  <span class="hljs-attr">VERSION:</span> <span class="hljs-string">"0.0.4"</span> <span class="hljs-comment"># To Change if needs be</span>

<span class="hljs-attr">stages:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">upload</span>

<span class="hljs-attr">upload:</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">upload</span>
  <span class="hljs-attr">only:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">apt-get</span> <span class="hljs-string">update</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">apt-get</span> <span class="hljs-string">install</span> <span class="hljs-string">-y</span> <span class="hljs-string">curl</span> <span class="hljs-string">wget</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ./model.pth "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/example-ml-packaging-pipeline/${VERSION}/model.pth"'</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ./run_mnist.py "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/example-ml-packaging-pipeline/${VERSION}/run_mnist.py"'</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ./requirements.txt "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/example-ml-packaging-pipeline/${VERSION}/requirements.txt"'</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ./INSTRUCTION.md "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/example-ml-packaging-pipeline/${VERSION}/INSTRUCTION.md"'</span>
</code></pre>
<p>The anatomy of this <code>.yml</code> file is very bare bones. We have only one stage in our pipeline which is the <code>upload</code> stage. </p>
<p>In the upload stage, we will run the <code>script</code> section only when the <code>master</code> branch gets updated. The script that we ran is simply using <code>curl</code> to transfer the data from this repository (4 files) into the package registry.</p>
<p>Let's take a look at the anatomy of the <code>curl</code> command we are using:</p>
<pre><code class="lang-python"> - <span class="hljs-string">'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ./NAME_OF_FILE "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/example-ml-packaging-pipeline/${VERSION}/NAME_OF_FILE"'</span>
</code></pre>
<ul>
<li><code>--header</code> is used to tell curl that you will be including an <a target="_blank" href="https://curl.se/docs/manpage.html#-H">extra header to the request</a>.</li>
<li><code>JOB-TOKEN</code> is our header and <code>$CI_JOB_TOKEN</code> is its value. It's a variable that lives within Gitlab servers when a job is created</li>
<li><code>--upload-file</code> is a flag to tell that we will transfer a <a target="_blank" href="https://curl.se/docs/manpage.html#-T">local file to the remote URL</a>.</li>
<li><code>./NAME_OF_FILE</code> is the name of the local file we want to transfer.</li>
<li><code>${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/example-ml-packaging-pipeline/${VERSION}/NAME_OF_FILE</code> is the location of the remote URL that we want to transfer a file. </li>
</ul>
<p>Here <code>$CI_API_V4_URL</code> is the URL of the Gitlab API we are using, <code>$CI_PROJECT_ID</code> is defined within Gitlab CI as the id for our project, and finally <code>VERSION</code> is the version number we defined at the top of the <code>.yml</code> file.</p>
<p>That's it! When you update the main branch to the remote repository on Gitlab it will fire up a pipeline that will run your packaging job.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/gitlab-ci.png" alt="Image" width="600" height="400" loading="lazy">
<em>The job will then be available and you will be able to check the trace on Gitlab!</em></p>
<p>You and your teammates will be able to see the document in the package registry section and get the right versioned files in the package:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/package-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>This is our v.0.0.5 of the example package!</em></p>
<p>To get a more complete idea of what is possible with the Packages API, head over to the <a target="_blank" href="https://docs.gitlab.com/ee/api/packages.html">official documentation</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial you've learn how to bundle, upload, and automatize a machine learning model packaging using Gitlab CI/CD. </p>
<p>Congratulation! 🎉🎉🎉</p>
<p>There is still a lot more you can do with Gitlab CI/CD, for instance:</p>
<ul>
<li>Add a testing stage before the bundling in order to make sure that there is no regression in the code.</li>
<li>Add a testing stage after the bundling to make sure that the performance of your model is satisfactory in terms of inference latency.</li>
<li>Use a more optimized version of the model with TorchScript.</li>
<li>Add automatic social notification of new release after the upload step.</li>
</ul>
<p>To learn more about Gitlab CI/CD the official docs is a great place to start out, and the <a target="_blank" href="https://docs.gitlab.com/ee/ci/quick_start/">get started section is very beginner friendly</a>.</p>
<p>If you want to read more of this type of content, check out my <a target="_blank" href="https://grad4.com/en/category/blog/grad4-engineering-blog/">mechanical/software engineering articles</a>. If you want to discuss any of this feel free to send me a DM on <a target="_blank" href="https://www.linkedin.com/in/yacine-mahdid-809425163/">LinkedIn</a> or <a target="_blank" href="https://twitter.com/CodeThisCodeTh1">Twitter</a> :) </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to write Bash one-liners for cloning and managing GitHub and GitLab repositories ]]>
                </title>
                <description>
                    <![CDATA[ Few things are more satisfying to me than one elegant line of Bash that automates hours of tedious work.  As part of some recent explorations into automatically re-creating my laptop with Bash scripts (post to come!), I wanted to find a way to easily... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/bash-one-liners-for-github-and-gitlab/</link>
                <guid isPermaLink="false">66bd8f13ffb0fc5947cc911a</guid>
                
                    <category>
                        <![CDATA[ Bash ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitLab ]]>
                    </category>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Victoria Drake ]]>
                </dc:creator>
                <pubDate>Wed, 07 Aug 2019 15:14:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/08/cover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Few things are more satisfying to me than one elegant line of Bash that automates hours of tedious work. </p>
<p>As part of some recent explorations into automatically re-creating my laptop with Bash scripts (post to come!), I wanted to find a way to easily clone my GitHub-hosted repositories to a new machine. After a bit of digging around, I wrote a one-liner that did just that. </p>
<p>Then, in the spirit of not putting all our eggs in the same basket, I wrote another one-liner to automatically create and push to GitLab-hosted backups as well. Here they are.</p>
<h1 id="heading-a-bash-one-liner-to-clone-all-your-github-repositories">A Bash one-liner to clone all your GitHub repositories</h1>
<p>Caveat: you’ll need a list of the GitHub repositories you want to clone. The good thing about that is it gives you full agency to choose just the repositories you want on your machine, instead of going in whole-hog.</p>
<p>You can easily clone GitHub repositories without entering your password each time by using HTTPS with your <a target="_blank" href="https://help.github.com/en/articles/caching-your-github-password-in-git">15-minute cached credentials</a> or, my preferred method, by <a target="_blank" href="https://help.github.com/en/articles/connecting-to-github-with-ssh">connecting to GitHub with SSH</a>. For brevity I’ll assume we’re going with the latter, and our SSH keys are set up.</p>
<p>Given a list of GitHub URLs in the file <code>gh-repos.txt</code>, like this:</p>
<pre><code class="lang-txt">git@github.com:username/first-repository.git
git@github.com:username/second-repository.git
git@github.com:username/third-repository.git
</code></pre>
<p>We run:</p>
<pre><code class="lang-bash">xargs -n1 git <span class="hljs-built_in">clone</span> &lt; gh-repos.txt
</code></pre>
<p>This clones all the repositories on the list into the current folder. This same one-liner works for GitLab as well, if you substitute the appropriate URLs.</p>
<h2 id="heading-whats-going-on-here">What’s going on here?</h2>
<p>There are two halves to this one-liner: the input, counterintuitively on the right side, and the part that makes stuff happen, on the left. We could make the order of these parts more intuitive (maybe?) by writing the same command like this:</p>
<pre><code class="lang-bash">&lt;gh-repos.txt xargs -n1 git <span class="hljs-built_in">clone</span>
</code></pre>
<p>To run a command for each line of our input, <code>gh-repos.txt</code>, we use <code>xargs -n1</code>. The tool <code>xargs</code> reads items from input and executes any commands it finds (it will <code>echo</code> if it doesn’t find any). By default, it assumes that items are separated by spaces; new lines also works and makes our list easier to read. The flag <code>-n1</code>tells <code>xargs</code> to use <code>1</code> argument, or in our case, one line, per command. We build our command with <code>git clone</code>, which <code>xargs</code> then executes for each line. Ta-da.</p>
<h1 id="heading-a-bash-one-liner-to-create-and-push-many-repositories-on-gitlab">A Bash one-liner to create and push many repositories on GitLab</h1>
<p>GitLab, unlike GitHub, lets us do this nifty thing where we don’t have to use the website to make a new repository first. We can <a target="_blank" href="https://gitlab.com/help/gitlab-basics/create-project#push-to-create-a-new-project">create a new GitLab repository from our terminal</a>. The newly created repository defaults to being set as Private, so if we want to make it Public on GitLab, we’ll have to do that manually later.</p>
<p>The GitLab docs tell us to push to create a new project using <code>git push --set-upstream</code>, but I don’t find this to be very convenient for using GitLab as a backup. As I work with my repositories in the future, I’d like to run one command that pushes to both GitHub <em>and</em> GitLab without additional effort on my part.</p>
<p>To make this Bash one-liner work, we’ll also need a list of repository URLs for GitLab (ones that don’t exist yet). We can easily do this by copying our GitHub repository list, opening it up with Vim, and doing a <a target="_blank" href="https://vim.fandom.com/wiki/Search_and_replace">search-and-replace</a>:</p>
<pre><code class="lang-bash">cp gh-repos.txt gl-repos.txt
vim gl-repos.txt
:%s/\&lt;github\&gt;/gitlab/g
:wq
</code></pre>
<p>This produces <code>gl-repos.txt</code>, which looks like:</p>
<pre><code class="lang-txt">git@gitlab.com:username/first-repository.git
git@gitlab.com:username/second-repository.git
git@gitlab.com:username/third-repository.git
</code></pre>
<p>We can create these repositories on GitLab, add the URLs as remotes, and push our code to the new repositories by running:</p>
<pre><code class="lang-bash">awk -F<span class="hljs-string">'\/|(\.git)'</span> <span class="hljs-string">'{system("cd ~/FULL/PATH/" $2 " &amp;&amp; git remote set-url origin --add " $0 " &amp;&amp; git push")}'</span> gl-repos.txt
</code></pre>
<p>Hang tight and I’ll explain it; for now, take note that <code>~/FULL/PATH/</code> should be the full path to the directory containing our GitHub repositories.</p>
<p>We do have to make note of a couple assumptions:</p>
<ol>
<li>The name of the directory on your local machine that contains the repository is the same as the name of the repository in the URL (this will be the case if it was cloned with the one-liner above);</li>
<li>Each repository is currently checked out to the branch you want pushed, ie. <code>master</code>.</li>
</ol>
<p>The one-liner could be expanded to handle these assumptions, but it is the humble opinion of the author that at that point, we really ought to be writing a Bash script.</p>
<h2 id="heading-whats-going-on-here-1">What’s going on here?</h2>
<p>Our Bash one-liner uses each line (or URL) in the <code>gl-repos.txt</code> file as input. With <code>awk</code>, it splits off the name of the directory containing the repository on our local machine, and uses these pieces of information to build our larger command. If we were to <code>print</code> the output of <code>awk</code>, we’d see:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/FULL/PATH/first-repository &amp;&amp; git remote set-url origin --add git@gitlab.com:username/first-repository.git &amp;&amp; git push
<span class="hljs-built_in">cd</span> ~/FULL/PATH/second-repository &amp;&amp; git remote set-url origin --add git@gitlab.com:username/second-repository.git &amp;&amp; git push
<span class="hljs-built_in">cd</span> ~/FULL/PATH/third-repository &amp;&amp; git remote set-url origin --add git@gitlab.com:username/third-repository.git &amp;&amp; git push
</code></pre>
<p>Let’s look at how we build this command.</p>
<h3 id="heading-splitting-strings-with-awk">Splitting strings with <code>awk</code></h3>
<p>The tool <code>awk</code> can split input based on <a target="_blank" href="https://www.gnu.org/software/gawk/manual/html_node/Command-Line-Field-Separator.html">field separators</a>. The default separator is a whitespace character, but we can change this by passing the <code>-F</code> flag. Besides single characters, we can also use a <a target="_blank" href="https://www.gnu.org/software/gawk/manual/html_node/Regexp-Field-Splitting.html#Regexp-Field-Splitting">regular expression field separator</a>. Since our repository URLs have a set format, we can grab the repository names by asking for the substring between the slash character <code>/</code> and the end of the URL, <code>.git</code>.</p>
<p>One way to accomplish this is with our regex <code>\/|(\.git)</code>:</p>
<ul>
<li><code>\/</code> is an escaped <code>/</code> character;</li>
<li><code>|</code> means “or”, telling awk to match either expression;</li>
<li><code>(\.git)</code> is the capture group at the end of our URL that matches “.git”, with an escaped <code>.</code> character. This is a bit of a cheat, as “.git” isn’t strictly splitting anything (there’s nothing on the other side) but it’s an easy way for us to take this bit off.</li>
</ul>
<p>Once we’ve told <code>awk</code> where to split, we can grab the right substring with the <a target="_blank" href="https://www.gnu.org/software/gawk/manual/html_node/Fields.html#index-_0024-_0028dollar-sign_0029_002c-_0024-field-operator">field operator</a>. We refer to our fields with a <code>$</code> character, then by the field’s column number. In our example, we want the second field, <code>$2</code>. Here’s what all the substrings look like:</p>
<pre><code class="lang-bash">1: git@gitlab.com:username
2: first-repository
</code></pre>
<p>To use the whole string, or in our case, the whole URL, we use the field operator <code>$0</code>. To write the command, we just substitute the field operators for the repository name and URL. Running this with <code>print</code> as we’re building it can help to make sure we’ve got all the spaces right.</p>
<pre><code class="lang-bash">awk -F<span class="hljs-string">'\/|(\.git)'</span> <span class="hljs-string">'{print "cd ~/FULL/PATH/" $2 " &amp;&amp; git remote set-url origin --add " $0 " &amp;&amp; git push"}'</span> gl-repos.txt
</code></pre>
<h3 id="heading-running-the-command">Running the command</h3>
<p>We build our command inside the parenthesis of <code>system()</code>. By using this as the output of <code>awk</code>, each command will run as soon as it is built and output. The <code>system()</code> function creates a <a target="_blank" href="https://en.wikipedia.org/wiki/Child_process">child process</a> that executes our command, then returns once the command is completed. In plain English, this lets us perform the Git commands on each repository, one-by-one, without breaking from our main process in which <code>awk</code> is doing things with our input file. Here’s our final command again, all put together.</p>
<pre><code class="lang-bash">awk -F<span class="hljs-string">'\/|(\.git)'</span> <span class="hljs-string">'{system("cd ~/FULL/PATH/" $2 " &amp;&amp; git remote set-url origin --add " $0 " &amp;&amp; git push")}'</span> gl-repos.txt
</code></pre>
<h3 id="heading-using-our-backups">Using our backups</h3>
<p>By adding the GitLab URLs as remotes, we’ve simplified the process of pushing to both externally hosted repositories. If we run <code>git remote -v</code> in one of our repository directories, we’ll see:</p>
<pre><code class="lang-bash">origin  git@github.com:username/first-repository.git (fetch)
origin  git@github.com:username/first-repository.git (push)
origin  git@gitlab.com:username/first-repository.git (push)
</code></pre>
<p>Now, simply running <code>git push</code> without arguments will push the current branch to both remote repositories.</p>
<p>We should also note that <code>git pull</code> will generally only try to pull from the remote repository you originally cloned from (the URL marked <code>(fetch)</code> in our example above). Pulling from multiple Git repositories at the same time is possible, but complicated, and beyond the scope of this post. Here’s an <a target="_blank" href="https://astrofloyd.wordpress.com/2015/05/05/git-pushing-to-and-pulling-from-multiple-remote-locations-remote-url-and-pushurl/">explanation of pushing and pulling to multiple remotes</a> to help get you started, if you’re curious. The <a target="_blank" href="https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes">Git documentation on remotes</a> may also be helpful.</p>
<h1 id="heading-to-elaborate-on-the-succinctness-of-bash-one-liners">To elaborate on the succinctness of Bash one-liners</h1>
<p>Bash one-liners, when understood, can be fun and handy shortcuts. At the very least, being aware of tools like <code>xargs</code> and <code>awk</code> can help to automate and alleviate a lot of tediousness in our work. However, there are some downsides.</p>
<p>In terms of an easy-to-understand, maintainable, and approachable tool, Bash one-liners suck. They’re usually more complicated to write than a Bash script using <code>if</code> or <code>while</code> loops, and certainly more complicated to read. It’s likely that when we write them, we’ll miss a single quote or closing parenthesis somewhere; and as I hope this post demonstrates, they can take quite a bit of explaining, too. So why use them?</p>
<p>Imagine reading a recipe for baking a cake, step by step. You understand the methods and ingredients, and gather your supplies. Then, as you think about it, you begin to realize that if you just throw all the ingredients at the oven in precisely the right order, a cake will instantly materialize. You try it, and it works!</p>
<p>That would be pretty satisfying, wouldn’t it?</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A simple Git guide and cheat sheet for open source contributors ]]>
                </title>
                <description>
                    <![CDATA[ A go-to git cheat sheet for your open source contributions. If you’re reading this article, you already know that the benefit of open source contribution abounds. You can skip the article and navigate to the end if you’re here for the cheat sheet. Th... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-simple-git-guide-and-cheat-sheet-for-open-source-contributors/</link>
                <guid isPermaLink="false">66bb8f95d2bda3e4315491c4</guid>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitLab ]]>
                    </category>
                
                    <category>
                        <![CDATA[ open source ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Saheed Oladele ]]>
                </dc:creator>
                <pubDate>Fri, 12 Jul 2019 17:18:51 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9ca17a740569d1a4ca4ed1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A go-to git cheat sheet for your open source contributions.</p>
<p>If you’re reading this article, you already know that the benefit of open source contribution abounds. You can skip the article and navigate to the end if you’re here for the cheat sheet.</p>
<p>The common problem faced by aspiring open source contributors is how to take the first step <em>from fork to pull request</em>. After reading this article, you should be well equipped with all you need to make your first open source pull request.</p>
<p>Apart from making the process easier for you, the git workflow defined in this piece also makes your contributions look professional. This is especially useful in case you want to add your open source contributions to your portfolio.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-240.png" alt="Image" width="600" height="400" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@randyfath?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"&gt;Randy Fath / &lt;a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm<em>campaign=api-credit)</em></p>
<p>This article assumes you already know the steps to take to contribute to open source. If you don’t know, you may want to read <a target="_blank" href="https://rubygarage.org/blog/how-contribute-to-open-source-projects">this article written by Maryna</a>. This piece also assumes that you’ve already setup Git on your PC. If you haven’t, you may want to check the <a target="_blank" href="https://help.github.com/en/articles/set-up-git">setting up Git section of this article</a> and do that first.</p>
<h3 id="heading-step-1-fork-the-project">Step 1: Fork the project</h3>
<p>This is as simple as clicking a button on GitHub. Navigate to the repository of the project you want to contribute to, then click the fork button at the top right corner as illustrated in the picture below.</p>
<p><img src="https://lh4.googleusercontent.com/4u1uvX1dRTkG0RLXeWqt6N7-Ed2BeNiOfG8KgXsiOAE-quBpq2rDKS2d6dkxyEWbMThVJu4bgeqU9aKO-vhxyj5XULbRxpV0WedoctN0wm_RhgSyzg5ICn4aZUkk99BwBj2ugCBv" alt="Image" width="1040" height="631" loading="lazy"></p>
<p>After using the fork button, you’d now have the repository on your GitHub account.</p>
<h3 id="heading-step-2-clone-the-project-to-your-local-machine">Step 2: Clone the project to your local machine</h3>
<p>This is the simplest part of Git. Navigate to your forked repository (the repository is now one of your GitHub repositories). Follow steps 1 and 2 as shown in the image below to copy the clone address. This address should look like this: <code>https:[github.com/suretrust.com/freeCodeCamp.git](http://github.com/suretrust.com/freeCodeCamp.git)</code></p>
<p><img src="https://lh5.googleusercontent.com/lyeLwQ6uz-VcEFoQcEGNf5KQiSzaDz1iwefGwi4CAoxuqiOdUPBm_jxVz1GJMgjHYHYkzGIHKb1l7iPdTQ5OIu3WUzK_ouFHHGAruNe-WJVKBsWpPgyLD5EClWnj7kaxsszwFqHB" alt="Image" width="1040" height="631" loading="lazy"></p>
<p>Then, clone the project by typing <code>git clone &lt;the copied address&gt;</code> into your command terminal as shown below:</p>
<p><code>git clone [https://github.com/suretrust/freeCodeCamp.git](https://github.com/suretrust/freeCodeCamp.git)</code></p>
<h3 id="heading-step-3-create-upstream">Step 3: Create upstream</h3>
<p>The upstream is necessary to keep track of the difference between the forked repository that is on your Git account and the original repository. This is most useful if you want to contribute to a popular repository.</p>
<p>Some repositories merge pull requests hourly or less, so be safe and assume that the forked repository you have will be behind the original repository. </p>
<p><strong>Note that the upstream is in the freeCodeCamp repository and not your forked repository.</strong> Follow steps 1 and 2 as shown below to copy the upstream address:</p>
<p><img src="https://lh6.googleusercontent.com/-fIOwK3jSHRJQrtVdCbGYc_0xFxPt-I22JmCqIom7f5F53iKceawsfju-NBw_wQ5LtRmsk9gJB3qJfA28ujR01lhF8VQKvvercoigfnVUbKNHrgOalp4OXz5CH6tXX46ev7d6Acv" alt="Image" width="1040" height="631" loading="lazy"></p>
<p>To create a link to the original repository, copy and paste the following command into your terminal:</p>
<p><code>git remote add upstream &lt;upstream address&gt;</code></p>
<p>You can use <code>git pull upstream master</code> to confirm if there has been any change at the moment (from when you forked the repository to now).</p>
<h3 id="heading-step-4-create-the-branch-you-want-to-work-on">Step 4: Create the branch you want to work on</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-241.png" alt="Image" width="600" height="400" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@<em>zachreiner</em>?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit"&gt;Zach Reiner / &lt;a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm<em>campaign=api-credit)</em></p>
<p>It is nice to create a new branch whenever you want to contribute. This illustrates that the branch is only for that contribution you are about to make. It could be as small as fixing a typo or as large as implementing a new feature. Either way, it’s good practice to create a branch.</p>
<p>Another important part of the branch creation is naming. It is pleasing to use a name that a stranger who knows nothing about the repository can easily understand. If you want to add a login feature, for example, you could create a branch called <code>add-login-feature</code> or <code>login-feature</code>.</p>
<p>To create a branch type the following command into your terminal:</p>
<p><code>git checkout -b &lt;your branch name&gt;</code></p>
<p>This command will create the branch and navigate into it. If your branch name is login-feature, then you can use the following command:</p>
<p><code>git checkout -b login-feature</code></p>
<p><em><strong>Then add your contributions. After adding your contribution, move on to Step 5.</strong></em></p>
<h3 id="heading-step-5-git-add-and-commit-your-contributions">Step 5: Git add and commit your contributions</h3>
<p>This is quite simple as well. Stage and commit your changes by typing the following into your terminal.</p>
<p><code>git add .</code></p>
<p><code>git commit -m 'Commit message'</code></p>
<p>Now, you have the changes staged and committed. What next?</p>
<h3 id="heading-step-6-pull-from-upstream-to-the-branch">Step 6: Pull from upstream to the branch</h3>
<p>As I explained in step 4, this step is to merge any difference in the upstream into the branch so as to prevent conflicts.</p>
<p><code>git pull upstream &lt;branch name&gt;</code></p>
<p>This merges the upstream changes into your current branch.</p>
<h3 id="heading-step-7-push-to-the-branch-youre-working-on">Step 7: Push to the branch you’re working on</h3>
<p>Now, you are almost there. Push your changes to the branch you are working on as shown below:</p>
<p><code>git push origin &lt;branch-name&gt;</code></p>
<h3 id="heading-step-8-open-a-pull-request">Step 8: Open a pull request</h3>
<p>This is the final step for any open source contribution, you are simply saying ‘I have made some changes, would you mind adding it to the project?’.</p>
<p>You open a pull request and if the repository owner or members like what they see, they’ll merge it. Otherwise, they could make changes then merge or request for changes.</p>
<p>To open a pull request, navigate to the forked repository as shown below. You’ll see your last push branch <code>‘login-feature’</code>, then click on <code>‘compare and pull request’</code>.</p>
<p><img src="https://lh5.googleusercontent.com/GHcFpgR70pKrxpyhfNDnPRvVluSPF-gz2ICUKv1Q3uxZKEaBcwv32E8Rh7d-5yNS9uvGXWzCcoc22KBbddEOybzP7BkONlKdqXXmFtdcqIm6AU5ebZjAZeFV0iL7PMulwrnT8MnA" alt="Image" width="887" height="559" loading="lazy"></p>
<p>Explain clearly, the changes you made, then open a pull request as shown below:</p>
<p><img src="https://lh4.googleusercontent.com/4yGQB3_1-2IyGDiOAfNec1yyoMXyvEzUAEcShTx4xf8_DU5vgfhFN0Uihn0A-BZzKGJkeCnjDbQkXT_AKtTCsgAnXK6vDcIWuvWY5ETmUH4MORXT7kgz_4qKVnD2zj1bLcQRTWf1" alt="Image" width="994" height="567" loading="lazy"></p>
<p>And that’s it. :) You can now go ahead and contribute like a PRO!</p>
<h2 id="heading-git-cheat-sheet-for-open-source-contributors">Git cheat sheet for open source contributors</h2>
<p><img src="https://lh5.googleusercontent.com/ZoVAty5u4vZaFdwBXh2fpsPQMsgW_3qxnt_dCo8Qn5ayk-fdvIZh6D6jSY_GdUhW8yUZvIIaBc_6WoLTyWseX3M8m7yPIzA8f4fL6X_oikH5wRcykopNH1KPI7eEuiz_8-M-jnZm" alt="Image" width="1040" height="631" loading="lazy"></p>
<p>Peace out and happy contributing!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to add an SSL certificate and custom Namecheap domain to a GitLab Pages site ]]>
                </title>
                <description>
                    <![CDATA[ By Erica Pisani Adding an SSL certificate and custom Namecheap domain to a GitLab Pages site can be a bit more challenging than it seems. Crucial pieces of the setup information live in sometimes dense documentation across different sites. It can be ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-an-ssl-certificate-and-custom-namecheap-domain-to-a-gitlab-pages-site-323f8f3ce642/</link>
                <guid isPermaLink="false">66c34ed8465d1b2f886ba417</guid>
                
                    <category>
                        <![CDATA[ GitLab ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SSL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 30 Oct 2018 16:57:08 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*9QSXL-RF1rxq9xyoPZjFKw.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Erica Pisani</p>
<p>Adding an SSL certificate and custom Namecheap domain to a GitLab Pages site can be a bit more challenging than it seems.</p>
<p>Crucial pieces of the setup information live in sometimes dense documentation across different sites. It can be hard to tell if you’ve set things up correctly given that you have to wait hours to confirm your changes have propagated.</p>
<p>Even when you know something is wrong, you can’t always tell what. This makes debugging the problem frustrating and challenging to fix.</p>
<p>This guide aims to make the process a bit more straightforward and less frustrating. It assumes that you’ve:</p>
<ul>
<li>Already set up your project on GitLab Pages and are able to access it by entering <code>&lt;your-username&gt;.gitlab.io/&lt;your-proj</code>ect-name&gt; in your browser</li>
<li>Have purchased a custom domain name along with an SSL certificate through Namecheap</li>
</ul>
<h3 id="heading-step-1-activate-the-ssl-certificate"><strong>Step 1: Activate the SSL certificate</strong></h3>
<p>In Namecheap, go to the ‘Product List’ &gt; ‘SSL Certificates’ page. You should see a list of SSL certificates that you have purchased, but have not yet activated. Click ‘Activate’ on the SSL certificate that you wish to activate for your site.</p>
<h3 id="heading-step-2-generate-the-ssl-certificate-request"><strong>Step 2: Generate the SSL certificate request</strong></h3>
<p>You should have been brought to a page that looks like the following:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*u9hG-Wrtm22y9byC5SmQTA.png" alt="Image" width="800" height="438" loading="lazy"></p>
<p>In order to generate a CSR, you’ll need to run the following command in your terminal: <code>openssl req -new -newkey rsa:2048 -nodes -keyout &lt;your-domain-name&gt;.key -out &lt;your-domain-n</code>ame&gt;.csr.</p>
<p>A private key will be generated as a result of that command. <strong>DO NOT LOSE THIS KEY.</strong> You will need it later on when you go to install your certificate on GitLab. Should you lose it, you will have to submit another CSR request.</p>
<p>You can read the nitty-gritty details <a target="_blank" href="https://www.namecheap.com/support/knowledgebase/article.aspx/9446/0/apache-opensslmodsslnginx">here</a> about generating a CSR if you wish, but the TL;DR is:</p>
<ul>
<li>It’s strongly encouraged that you fill out all the required fields. Your CSR could be rejected during activation of you do not. If you are filling this CSR out for a personal or hobby site, you can enter <code>NA</code> for the ‘Organization’ and ‘Organization Unit’ fields.</li>
<li>If the certificate is being issued for a specific subdomain, you need to specify the subdomain in the ‘Common Name’ field. Example: <code>subdomain.ssl-certificate-host.com</code></li>
<li>If the certificate is meant to be a wildcard certificate, the domain should start with an asterisk. Example: <code>*.ssl-certificate-host.com</code></li>
</ul>
<p>For the purposes of this guide, the assumption will be made that you are getting the certificate for something like <code>&lt;example-domain&amp;g</code>t;.com .</p>
<p>Once you’ve run the command, you should have a <code>.csr</code> and <code>.key</code> file in your working directory. Open the <code>.csr</code> file, and copy the contents in it. It should have the header <code>----- BEGIN CERTIFICATE REQUEST -----</code>.</p>
<p>Paste the contents of the file into the <code>Enter CSR</code> field. The page will automatically fill out the domain field on the form based on the information in the CSR.</p>
<p>Once you click ‘Next’, you should see the following page:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*VYlKqYMsnvyaF0smz4q_8w.png" alt="Image" width="800" height="355" loading="lazy"></p>
<p>Check that the information is correct, and then click ‘Next’ again to go to the ‘Confirm you own the domain’ step.</p>
<h3 id="heading-step-3-confirm-you-own-the-domain"><strong>Step 3: Confirm you own the domain</strong></h3>
<p>There are a few different options that are available to you in order to do this:</p>
<ul>
<li>Email</li>
<li>HTTP-based</li>
<li>DNS-based</li>
</ul>
<p>I personally have had issues validating through email, so for the purposes of this guide, select ‘DNS-based’. This requires you to set up a <code>CNAME</code> value in your domain’s DNS settings, which we will cover later on in this guide.</p>
<p>For now, click ‘Next’ after selecting ‘DNS-based’, but if you change your mind about this form of validation later on, it’s possible to change it.</p>
<h3 id="heading-step-4-specify-who-will-receive-the-ssl-file"><strong>Step 4: Specify who will receive the SSL file</strong></h3>
<p>Confirm that the email in the field is correct. This is the email that will receive the certificate once it’s been activated.</p>
<h3 id="heading-step-5-review-and-submit"><strong>Step 5: Review and Submit</strong></h3>
<p>Confirm the information shown is correct, and then click ‘Submit’.</p>
<h3 id="heading-step-6-set-up-the-cname-record-for-validating-ownership-of-the-domain"><strong>Step 6: Set up the <code>CNAME</code> record for validating ownership of the domain</strong></h3>
<p>Once you submit the form, you will be redirected to a page showing the SSL certificate details with a helpful notification window that looks like the following:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*rBxGLLFzyBqDRe1ROxYaTw.png" alt="Image" width="800" height="147" loading="lazy"></p>
<p>Click on the link for the DNS-based DCV method. You’ll be brought to a page that shows information that you entered earlier, such as:</p>
<ul>
<li>The domain name</li>
<li>The type of web server that will have the certificate installed (should be Apache, Nginx, cPanel, or other)</li>
<li>DCV Methods In Use</li>
</ul>
<p>Access the dropdown options for the ‘Edit Methods’ button to the right of ‘DCV Methods in Use’ in order to access and click the ‘Get Record’ option.</p>
<p>A popover will appear showing the <code>CNAME</code> record you need to set up in order to confirm ownership of the domain. Copy these values to an empty text file as you’ll need to go to the ‘Advanced DNS’ page for your domain. This is accessible through ‘Dashboard’ or ‘Domain List’ &gt; ‘Manage’ (besides your domain in the list) &gt; ‘Advanced DNS’.</p>
<p>Under the ‘Host Records’ section:</p>
<ul>
<li>Click ‘Add New Record’</li>
<li>Select ‘CNAME Record’.</li>
<li>Paste the values that you copied earlier from the ‘Get Record’ popover into the corresponding fields.</li>
</ul>
<p>Before you save those values though, there’s a bit of a ‘gotcha’.</p>
<p>As Namecheap points out in their <a target="_blank" href="https://www.namecheap.com/support/knowledgebase/article.aspx/9637/68/how-can-i-complete-the-domain-control-validation-dcv-for-my-SSL-certificate#dns">documentation</a>, they “add the domain name automatically to the values submitted during record creation”. This means that the domain name that appears in the ‘host’ value is a duplicated value. Remove <code>&lt;your-custom-domain&amp;g</code>t;.com at the end of the ‘host’ value and you’ll be good to go.</p>
<p>After you save that record, it’ll take a bit of time before the certificate is issued. Once you receive the certificate in your email, proceed to step 8. If you haven’t already though, let’s set up the additional records needed in order to send people to <code>&lt;your-username&gt;.gitlab.io/&lt;your</code>-project&gt; when <code>they enter &lt;your-cus</code>tom-domain&gt;.com.</p>
<h3 id="heading-step-7-set-up-your-host-records-in-namecheap"><strong>Step 7: Set up your host records in Namecheap</strong></h3>
<p>As outlined in GitLab’s <a target="_blank" href="https://docs.gitlab.com/ee/user/project/pages/getting_started_part_three.html#dns-txt-record">docs</a>, you’ll also need to prove on GitLab’s end of things that you own the custom domain that you want to serve your GitLab Pages site on.</p>
<p>As mentioned earlier, this guide assumes that you are just looking to use <code>example.com</code> (or <code>www.example.com</code>), so you’ll want to add the following host records:</p>
<ul>
<li>Type <code>A Record</code>, Host <code>@</code>, Value <code>35.185.44.232</code> (this is the current GitLab Pages IP at the time of writing)</li>
<li>Type <code>CNAME Record</code>, Host <code>www</code> , Value <code>example.com</code> (this ensures that people who enter the 'www’ subdomain (i.e: <code>www.example.com</code>) still reach your site)</li>
<li><em>Note: You won’t be able to enter this one until you’ve added the domain through the ‘New Pages Domain’ flow outlined in Step 8.</em> Type <code>TXT Record</code>, Host <code>@</code> , Value <code>gitlab-pages-verification-code=11112222aaaabbbb</code></li>
</ul>
<h3 id="heading-step-8-install-the-certificate-in-gitlab"><strong>Step 8: Install the certificate in GitLab</strong></h3>
<p>Head on over to the ‘Pages’ page of your GitLab project that you’re trying to set up (under ‘Settings’ &gt; ‘Pages’ in the sidebar).</p>
<p>To add your custom domain that GitLab serves your Pages site on, click on the ‘New Domain’ button on the top right. You should see something like the following:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*tjmYJy0dsUrhDJH0Re4PRw.png" alt="Image" width="800" height="401" loading="lazy"></p>
<p>Enter your custom domain (<code>example.com</code>) in the domain field, and then the next part is where it gets interesting.</p>
<p>If you try just to enter your certificate (<code>example_com.crt</code>) and your private key (generated when you initially sent the certificate request) in the fields, you’ll likely get a ‘Certificate is missing intermediates’ error.</p>
<p>This is because GitLab is using something like NGINX to receive requests on it’s Pages IP before routing the request to the correct site. Namecheap, in their <a target="_blank" href="https://www.namecheap.com/support/knowledgebase/article.aspx/9474/69/how-do-i-create-a-pem-file-from-the-certificates-i-received-from-you">documentation</a>, calls out that “it is required to combine your certificate with CA certificates in a single file”.</p>
<p>What this means for you is that you need to combine the text found in your <code>example_com.crt</code> and <code>example_com.ca-bundle</code> files in the ‘certificate field’. In the end you should have something like:</p>
<p>Add the private key to the last field, and you’re done. It will take time for the changes to propagate. If you check back in a few hours, you should see an indication beside your address in the URL bar showing that your connection to your site is now secure.</p>
<h3 id="heading-resourcesreferences"><strong>Resources/References</strong></h3>
<ul>
<li><a target="_blank" href="https://about.gitlab.com/features/pages/">https://about.gitlab.com/features/pages/</a></li>
<li><a target="_blank" href="https://docs.gitlab.com/ee/user/project/pages/getting_started_part_three.html#dns-txt-record">https://docs.gitlab.com/ee/user/project/pages/getting_started_part_three.html#dns-txt-record</a></li>
<li><a target="_blank" href="https://www.namecheap.com/support/knowledgebase/article.aspx/9474/69/how-do-i-create-a-pem-file-from-the-certificates-i-received-from-you">https://www.namecheap.com/support/knowledgebase/article.aspx/9474/69/how-do-i-create-a-pem-file-from-the-certificates-i-received-from-you</a></li>
<li><a target="_blank" href="https://www.namecheap.com/support/knowledgebase/article.aspx/9637/68/how-can-i-complete-the-domain-control-validation-dcv-for-my-SSL-certificate#dns">https://www.namecheap.com/support/knowledgebase/article.aspx/9637/68/how-can-i-complete-the-domain-control-validation-dcv-for-my-SSL-certificate#dns</a></li>
<li><a target="_blank" href="https://stackoverflow.com/a/49124195/2719852">https://stackoverflow.com/a/49124195/2719852</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to get GitLab to do periodic jobs for you in under a minute ]]>
                </title>
                <description>
                    <![CDATA[ By Moe Ibrahim What would technology be without a computer doing periodic work? Whether it’s your phone constantly checking your inbox for you, or getting timely alerts for weather or flight delays. What about a bitcoin vs Canadian dollar price servi... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/56-seconds-to-get-gitlab-to-do-periodic-jobs-for-you-6a731b977559/</link>
                <guid isPermaLink="false">66c34191160da468ed76f0f6</guid>
                
                    <category>
                        <![CDATA[ Bitcoin ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitLab ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 06 Mar 2018 00:15:49 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*HHVkCUSmaGkFPTx06jjZKw.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Moe Ibrahim</p>
<p>What would technology be without a computer doing periodic work?</p>
<p>Whether it’s your phone constantly checking your inbox for you, or getting timely alerts for weather or flight delays.</p>
<p>What about a bitcoin vs Canadian dollar price service, in just 56 seconds? No <em>IFTTT</em>, no <em>Zapier</em>, but no programming languages either — and no frameworks, no server or docker configuration, no Raspberry Pi, no AWS and no tests!</p>
<p>To make the example as universal as possible, we will only use 2 command lines:</p>
<ul>
<li>one to GET the bitcoin price from an API</li>
<li>and another to POST it to another service.</li>
</ul>
<p>Of course you can make this more useful by posting the price to Twitter, Twilio, Telegram, Slack and so on. But here we will simply post it to putsreq.com so we can inspect the POST request.</p>
<p>Then we will use GitLab-CI to schedule it to run everyday.</p>
<blockquote>
<p><strong>Level</strong> : All levels</p>
<p><strong>Requirements</strong> : Any web browser</p>
</blockquote>
<p>Let’s get you started :</p>
<ol>
<li><p><strong>Create a free account</strong> at <a target="_blank" href="https://gitlab.com/users/sign_in">gitlab.com</a> (20 seconds)</p>
</li>
<li><p><strong>Create a new Project :</strong> Click on the <strong><em>New Project</em></strong> button to create a new repo, and in the name field type <em>periodic-job</em> or any other name. (9 seconds)</p>
</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/erHlPeiJTmeTnQ4GoFSMtoYLR5V0zseO60H8" alt="Image" width="800" height="521" loading="lazy"></p>
<p>Then save it by clicking on <strong><em>Create Project</em></strong> (1 second).</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/bzyB3KG6wmQ9Jc01n1FhAdyF6BBhCjC9pATv" alt="Image" width="800" height="536" loading="lazy"></p>
<ol start="3">
<li><strong>Create a .gitlab-ci.yml file in this new project:</strong> Click on <strong><em>New File</em></strong>, copy and paste the following snippet into the .gitlab-ci.yml file, then click save (5 seconds)</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ojeKxdcQUHLAGUO0y8g517nuss9rsKANxpt-" alt="Image" width="800" height="538" loading="lazy"></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/sFPB53USK5EYEvXza-ZyruSj0c9Qp3fx5-1R" alt="Image" width="800" height="532" loading="lazy"></p>
<pre><code>test:
</code></pre><pre><code> script:
</code></pre><pre><code> - btc=$(curl https:<span class="hljs-comment">//min-api.cryptocompare.com/data/price?fsym=BTC\&amp;tsyms=CAD)</span>
</code></pre><pre><code>- curl -i -X POST https:<span class="hljs-comment">//putsreq.com/wkDdMQWhaOyalisaIe49 — data ‘price=CA$ ‘“${btc//[0-9\.]/}”</span>
</code></pre><p>These are basically two simple commands. Here we can go further and add</p>
<p><a target="_blank" href="https://hackernoon.com/71-seconds-to-build-your-free-custom-webhook-illustrated-step-by-step-7a09b9e240ba"><em>if [ $btc -ge 15000 -a $btc -lt 7000 ]; then</em></a></p>
<p>conditions, or even run a full bash script file, but let’s keep it simple.</p>
<p>Click on the <strong><em>Commit changes</em></strong> button, and this will trigger it to build and run.</p>
<ol start="4">
<li><strong>Schedule it to run everyday:</strong> click on the CI/CD icon to expand the menu, and select Schedules to set up a name and a timer for your periodic job to trigger. (11 seconds)</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/LPe0diYgDp3FtDob-daYQgz9yQp3NIbpcaB8" alt="Image" width="800" height="491" loading="lazy"></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/myy9E9YueotL6uCnQzIj64S7WvNZgH0nSvI9" alt="Image" width="800" height="529" loading="lazy">
<em>click on <strong>New schedule</strong> button</em></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/hORLN61TKEGqsCLm4Dus6l0mC0hEp1kDxc7i" alt="Image" width="800" height="550" loading="lazy">
<em>Type in a name for the new schedule <strong>daily-bitcoin-price-job</strong>, select to run it daily then click <strong>Save</strong></em></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/2dK2LU1YHfEwHK3Mhzs82VpHdR3uXncbyG-L" alt="Image" width="800" height="541" loading="lazy">
<em>Your scheduled job has been saved</em></p>
<ol start="5">
<li>Congrats! You’re done. Go to to <a target="_blank" href="https://putsreq.com/wkDdMQWhaOyalisaIe49/inspect">this link in putsreq.com</a> to see it in action. (10 seconds)</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Yhx54rH7S8jocMDTd8NB0F6-RwySKIoDLT5Y" alt="Image" width="800" height="533" loading="lazy"></p>
<p>This job will run everyday as long as your free 2000/month build minutes do not run out.</p>
<p>We haven’t even scratched the surface of what we can do with GitLab-CI — just think of all the possibilities of using it to create webhooks or <a target="_blank" href="https://medium.com/@YYC_Ninja/99-seconds-to-make-bitcoin-call-your-phone-number-a8cbd9740f76">connecting it to IFTTT</a> and Zapier, which in turn would connect it to hundreds of services.</p>
<p>In <a target="_blank" href="https://medium.com/@YYC_Ninja/71-seconds-to-build-your-free-custom-webhook-illustrated-step-by-step-7a09b9e240ba">the next article</a> we will go through what we have just done, and how we can take it up a notch and create a webhook and use it to post to social media.</p>
<p>You can find the <a target="_blank" href="https://gitlab.com/ninjayoto/my-periodic-jobs/tree/master">sample code here</a>, and you can read the <a target="_blank" href="https://gitlab.com/ninjayoto/my-periodic-jobs/-/jobs">build logs here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to setup CI on GitLab using Docker ]]>
                </title>
                <description>
                    <![CDATA[ By Ying Kit Yuen An example using Docker to test and build your pipeline In the past you may have tried different tools to manage the deployment of your applications effectively. In this tutorial, I’ll be showing you a quick and easy way to set up c... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-setup-ci-on-gitlab-using-docker-66e1e04dcdc2/</link>
                <guid isPermaLink="false">66c354a971e87702d4e5b692</guid>
                
                    <category>
                        <![CDATA[ coding ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Continuous Integration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitLab ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 12 Jan 2018 19:11:31 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*b7KBZqZQ_VZ3kFu6lQQaYQ.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Ying Kit Yuen</p>
<p>An example using Docker to test and build your pipeline</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*b7KBZqZQ_VZ3kFu6lQQaYQ.png" alt="Image" width="800" height="600" loading="lazy"></p>
<p>In the past you may have tried different tools to manage the deployment of your applications effectively. In this tutorial, I’ll be showing you a quick and easy way to set up continuous integration for your environment using GitLab and Docker. So lets get started.</p>
<p>Here is what we’ll be setting up:</p>
<ul>
<li>Version control</li>
<li>Issue Tracker</li>
<li>Documentation</li>
<li>Continuous Integration</li>
<li>Continuous Delivery</li>
<li>Repository (artifacts/docker images)</li>
</ul>
<p>Tools like <a target="_blank" href="https://jenkins.io/">Jenkins</a> are good for continuous integration and delivery. <a target="_blank" href="https://www.mantisbt.org/">Mantis</a> helps with issue tracking. But to improve the efficiency and quality of our project, we need to bring all these tools together. For example, we may want a git commit hook up with existing issues, or trigger an automated test after pushing commits to the master branch.</p>
<p>Usually, most tools already provide out of the box integration with other common services but it is still difficult to configure them at times. Moreover, the workflow would be broken if any one of the services in the chain go down. So it would be great if there were a single platform which could fulfil all these requires and that is why we’ve chosen <a target="_blank" href="https://gitlab.com">GitLab</a>.</p>
<h3 id="heading-gitlab-ci">GitLab CI</h3>
<p><a target="_blank" href="https://gitlab.com">GitLab.com</a> is a SAAS based service where you can host your Git repository, track issues and write the wiki in markdown. <a target="_blank" href="https://about.gitlab.com/features/gitlab-ci-cd/">GitLab CI</a> also allows you to setup continuous integration utilizing any Docker image available on <a target="_blank" href="https://hub.docker.com/">Docker Hub</a>. Let’s take a look at the following example.</p>
<h4 id="heading-the-gitlab-ci-yml">The GitLab CI YML</h4>
<p>GitLab CI uses a YAML file <code>.gitlab-ci.yml</code> to define the project configuration, which includes a definition of all the stages that need to be run after a CI/CD pipeline is triggered in response to a git push/merge. In this example, we have a simple Node.js project and we would like to make sure the code is good by linting and running a unit test. To play along, fork this <a target="_blank" href="https://gitlab.com/ykyuen/gitlab-ci-demo">repository</a> and check it out.</p>
<p>In the above YAML configuration file we defined 3 stages. Each stage is just a gulp task defined in <code>gulpfile.js</code>. Anyone could run the task locally as long as the have Node.js installed. But in <a target="_blank" href="https://about.gitlab.com/features/gitlab-ci-cd/">GitLab CI</a>, we only need to mention which Docker image is needed. In our case, that is node:6.11.2. Furthermore, this image attribute could be defined within the stage definition so that you could use different tool for each stage.</p>
<h4 id="heading-the-stage-definition">The stage definition</h4>
<p>Let’s take a deeper look in the stage definition.</p>
<p>The attributes of <code>before_script</code> and <code>script</code> can have multiple values (array in .yml). And if the script execution fails, the stage will be classified as failed.</p>
<h4 id="heading-trigger-the-pipeline">Trigger the pipeline</h4>
<p>Just make some changes on the master branch and you can find the pipeline running on the <code>CI / CD -&gt; Pipel</code>ine page.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*Rtj97p-NEzbJjOe2Lh3-8A.jpeg" alt="Image" width="800" height="361" loading="lazy">
<em>The pipeline history</em></p>
<h4 id="heading-view-the-stage-in-detail">View the stage in detail</h4>
<p>Click on a specific pipeline and you can read the console output of each stage. This is useful when the stage/job fails.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*t_0-lH0Hi05sEBeK8XzDIg.jpeg" alt="Image" width="800" height="494" loading="lazy">
<em>The stage output</em></p>
<h3 id="heading-the-benefits-of-using-gitlab-ci-with-docker">The benefits of using GitLab CI with Docker</h3>
<p>Different projects may require different dependencies such as Node.js, Ant, Maven. In the past when using a tool such as <a target="_blank" href="https://jenkins.io/">Jenkins</a>, I’d have to make sure that all of these are installed on the server. By using <a target="_blank" href="https://www.docker.com/">Docker</a>, the developer can reference dependencies available on <a target="_blank" href="https://hub.docker.com/">Docker Hub</a> without asking the server admin to setup such dependencies on the server each time. Actually Jenkins also has a pipeline plugin and it could work with Docker to serve exactly the same purpose. But extra effort on integrating Jenkins with version control is needed and as I mentioned before.</p>
<p>Although i prefer using <a target="_blank" href="https://about.gitlab.com/features/gitlab-ci-cd/">GitLab CI</a>, it doesn’t mean it could completely replace <a target="_blank" href="https://jenkins.io/">Jenkins</a>. <a target="_blank" href="https://jenkins.io/">Jenkins</a> offers a configurable user interface which is convenient for non-developers such as QA to execute certain tasks like deployments and integration tests.</p>
<h3 id="heading-pick-a-suitable-tool-it-doesnt-have-to-be-perfect">Pick a suitable tool - It doesn’t have to be perfect</h3>
<p>The key is not about choosing the perfect tool. Instead it is more about the people who use it. So before searching for a new tool, try to identify the problem which you would like to solve first.</p>
<p>— Originally posted on <a target="_blank" href="https://blog.boatswain.io/post/a-simple-gitlab-ci-example/">Boatswain Blog</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Catch bugs systematically: how to build a GitLab CI testing pipeline in 4 steps ]]>
                </title>
                <description>
                    <![CDATA[ By Joyz Your first app is a hit the day it’s launched. But one week later, you realize that it has no retention. You discover that this is because whenever a user clicks the “send” button, their comments get posted twice. The bug was so minor, but it... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/4-steps-to-build-an-automated-testing-pipeline-with-gitlab-ci-24ccab95535e/</link>
                <guid isPermaLink="false">66c341651730faaceeb438c7</guid>
                
                    <category>
                        <![CDATA[ Continuou ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitLab ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 14 Aug 2017 20:05:02 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*3HCwOHLXPebHuA-2oe_FMA.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Joyz</p>
<p>Your first app is a hit the day it’s launched. But one week later, you realize that it has no retention. You discover that this is because whenever a user clicks the “send” button, their comments get posted twice.</p>
<p>The bug was so minor, but it killed your momentum. But that’s okay. For your second app, you and your partner check more carefully. You both click, click, click your app all day and night to prevent minor bugs like that from happening again.</p>
<p>For one app, that’s okay. But after a year, you have a company building 7 apps on different platforms, including web, iOS, and Android. Your team now does code review before any app launch. You test through the apps and do your clicking before they’re shipped. But your nightmare from app #1 returns: users drop the app and this time it’s because their posts showing strange characters when they type an emoji. You end up with 1-star ratings after launch.</p>
<p>There are 3 types of product-making companies: those who do not test, those who test, and those who test fast, accurately and frequently.</p>
<p>Is an automated testing system with continuous integration (CI) just a dream? CI seems like a “nice-to-have”, especially since services that run tests and generate reports like <a target="_blank" href="https://saucelabs.com/">Sauce Labs</a>, <a target="_blank" href="https://www.browserstack.com/">BrowserStack</a>, <a target="_blank" href="https://support.smartbear.com/testcomplete/">Test Complete</a> are expensive. The good news is, there are many free and popular tools out there that you can mix and match to set up a free automated testing system. As a QA tester, I figured out a free testing pipeline setup, so I’m sharing it to save you time and money.</p>
<p>Why did my company want to set up an automated system? Here are some reasons below:</p>
<ul>
<li>We hate manual repetitive jobs that are prone to human error.</li>
<li>We want a smoother process (test it when there is a code update), and reduce the waiting time.</li>
<li>We want to schedule the tests and make them under control.</li>
</ul>
<h3 id="heading-setting-up-your-ui-tests">Setting up your UI tests</h3>
<p>This post introduces a practical pipeline setup that can run <strong>web-based</strong> UI (User Interface) tests automatically and continuously. The later part maybe a bit technical but it’s quite fun to build!</p>
<p>I have set up the whole system with these free and open-source tools:</p>
<ul>
<li><a target="_blank" href="http://www.seleniumhq.org/">Selenium</a> — to script and automate browsers to perform tests</li>
<li><a target="_blank" href="https://www.docker.com/">Docker</a> — to build an image for test environment and ship it fast</li>
<li><a target="_blank" href="https://about.gitlab.com/gitlab-ci/">Gitlab CI</a> — to trigger, build and run the test upon code updates</li>
<li><a target="_blank" href="https://skygear.io/">Skygear</a> — to save test result for report on demand</li>
</ul>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*3HCwOHLXPebHuA-2oe_FMA.jpeg" alt="Image" width="800" height="600" loading="lazy">
<em>Setting up takes 4 steps. Here we go!</em></p>
<h3 id="heading-step-1-write-the-script-and-run-it-locally">Step #1: Write the script and run it locally</h3>
<p>First of all, we write the test script, to let our original manual test run automatically. <a target="_blank" href="http://www.seleniumhq.org/">Selenium</a> is a well-known tool for web automation. It supports different client languages including Java, Ruby, Python, etc.</p>
<p>Here’s an example on perform a button click on a website in Python.</p>
<blockquote>
<p>Update: Added usage of Chrome official headless mode (details <a target="_blank" href="https://medium.com/@joyzoursky/recent-updates-6264d1e5d42f">here</a>).</p>
</blockquote>
<p>With the idea of a basic unit test model, we could easily identify these three major components in the script:</p>
<ul>
<li>Set up</li>
<li>Run test case</li>
<li>Tear down</li>
</ul>
<p>In this script, it will run <code>test_case_1</code> and <code>test_case_2</code> respectively, both with <code>setUp</code> before the test and <code>tearDown</code> after the test. We use <a target="_blank" href="https://docs.python.org/3/library/unittest.html">unittest</a> as our testing framework in this example. Feel free to use what you like, such as <a target="_blank" href="http://doc.pytest.org/en/latest/">pytest</a> or <a target="_blank" href="http://nose.readthedocs.io/en/latest/">nose</a> in Python.</p>
<p>You can add more test cases, such as filling in forms and clicking on elements, depending on your website's interface.</p>
<h3 id="heading-step-2-build-an-image-with-your-testing-environment">Step #2: Build an image with your testing environment</h3>
<p>Test running requires a clean environment. To create a clean environment, we definitely do not want to set up a real machine every time and wait for hours to install all the software needed. The container concept helps.</p>
<p><a target="_blank" href="https://www.docker.com/">Docker</a> helps you build your testing environment into an image. The image includes all the software that needs to be pre-installed and run on that container like a virtual machine. With Docker, you can just create a new container and pull the same image every time you want to start over from your default environment.</p>
<p>To perform our test with the Selenium Python client, we want our image to pre-install the following:</p>
<ul>
<li>Python</li>
<li>Google Chrome</li>
<li>Chrome driver</li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Xvfb">Xvfb</a></li>
</ul>
<p>Xvfb is a virtual display server that helps you to launch a browser in <a target="_blank" href="http://elementalselenium.com/tips/38-headless">a headless mode</a> (without display). It is necessary to run the UI tests in a container. It cannot connect to a display output to show the browser visually.</p>
<p>Then, we will also install the Selenium package inside the container. Not all projects need the same list of packages.</p>
<p>We create a <a target="_blank" href="https://docs.docker.com/engine/reference/builder/">Dockerfile</a>, build the image and upload to our <a target="_blank" href="https://cloud.docker.com/">Docker Cloud</a>.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*6O2QhCEpjoALjK1nzo0N7g.png" alt="Image" width="800" height="1152" loading="lazy"></p>
<p>You could find this image through this <a target="_blank" href="https://hub.docker.com/r/joyzoursky/python-chromedriver/">link</a>, or directly pull this image with this command:</p>
<pre><code>docker pull joyzoursky/python-chromedriver:<span class="hljs-number">3.6</span>-xvfb
</code></pre><p>Then you will have an environment ready for performing the UI tests.</p>
<blockquote>
<p>Update: Added new Docker image built with Xvfb deprecated (details <a target="_blank" href="https://medium.com/@joyzoursky/recent-updates-6264d1e5d42f">here</a>).</p>
</blockquote>
<h3 id="heading-step-3-set-up-gitlab-ci">Step #3: Set up GitLab CI</h3>
<p><a target="_blank" href="https://about.gitlab.com/">GitLab</a> provides a CI/CD Pipelines feature, to continuously build and run your projects. The setup is like other CI tools such as <a target="_blank" href="https://travis-ci.org/">Travis CI</a> or <a target="_blank" href="https://jenkins.io/">Jenkins.</a> This requires a <code>.gitlab-ci.yml</code>file to configure your build process.</p>
<p>Take a look at this example:</p>
<blockquote>
<p>Update: Added example with Xvfb deprecated (details <a target="_blank" href="https://medium.com/@joyzoursky/recent-updates-6264d1e5d42f">here</a>).</p>
</blockquote>
<p>When new codes are pushed to the repository, GitLab will look for <code>.gitlab-ci.yml</code> from the root directory, and trigger a build according to your settings.</p>
<p>In this script, it pulls the environment image from <code>joyzoursky/python-chromedriver:3.6-xvfb</code> in the first line. Then it installs the required packages like Selenium, sets variables needed, and then it starts the process.</p>
<p>Note that there are 2 stages of the build process in this example: <code>test</code> and <code>report</code>. In each stage, the jobs in that stage will be run <strong>concurrently.</strong> You can define tests in the same stage if they could run in sync.</p>
<p>Go to the Pipelines page to see the flow and completion here:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*yEi8rtmbGz0JYottZ188oA.png" alt="Image" width="761" height="527" loading="lazy"></p>
<p>So where do we run our tests actually?</p>
<p>GitLab hosts some shared runners which are free. By looking into the build log, we can find the container information in the first few lines:</p>
<pre><code>Running <span class="hljs-keyword">with</span> gitlab-ci-multi-runner <span class="hljs-number">1.10</span><span class="hljs-number">.4</span> (b32125f)Using Docker executor <span class="hljs-keyword">with</span> image joyzoursky/python-chromedriver:<span class="hljs-number">3.5</span> ...Pulling docker image joyzoursky/python-chromedriver:<span class="hljs-number">3.5</span> ...Running on runner<span class="hljs-number">-4e4528</span>ca-project<span class="hljs-number">-2749300</span>-concurrent<span class="hljs-number">-0</span> via runner<span class="hljs-number">-4e4528</span>ca-machine<span class="hljs-number">-1489737516</span><span class="hljs-number">-5e0</span>de836-digital-ocean<span class="hljs-number">-4</span>gb...
</code></pre><p>It shows the container name running on <a target="_blank" href="https://www.digitalocean.com/">Digital Ocean</a>.</p>
<p>Of course, you can also create your specific runners to run the test on your self-hosted machines. GitLab supports runners on different platforms including <a target="_blank" href="https://www.docker.com/">Docker</a> and <a target="_blank" href="https://kubernetes.io/">Kubernetes</a>. But, as GitLab is a new platform, it goes through many updates. So the specific runners may sometimes break when they are out-of-date. You should always refer to the <a target="_blank" href="https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master">official repository</a> when configuring the setup.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*JZJwNUSlvVrA-mF2noJiLQ.png" alt="Image" width="800" height="696" loading="lazy"></p>
<h3 id="heading-step-4-run-and-report-periodically">Step #4: Run and report periodically</h3>
<p>You may want to have your tests run periodically. You can achieve this by setting up <a target="_blank" href="https://en.wikipedia.org/wiki/Cron">cron jobs</a>, but you may not want to set up a server just to run a one-line cron job. My company’s open source serverless back-end is <a target="_blank" href="http://skygear.io">Skygear</a>. We can use it to write a simple cloud code function with the <a target="_blank" href="https://docs.skygear.io/guides/cloud-function/scheduled-tasks/python/">@every</a> decorator and trigger the test pipeline on an interval of time.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*yuASEuatDfLkSD3sfmKaqA.png" alt="Image" width="800" height="353" loading="lazy">
_[Skygear](https://skygear.io/" rel="noopener" target="<em>blank" title=")’s developer portal. Look for your Cloud Code Git URL.</em></p>
<ul>
<li>Login to your <a target="_blank" href="https://portal.skygear.io">Skygear portal</a></li>
<li>Find your Cloud Code Git URL</li>
<li>Clone the quick start codes</li>
<li>Edit to add the little piece of code below</li>
<li>Push the codes and the cron job will trigger the test every hour</li>
</ul>
<blockquote>
<p>Update: Used GitLab 10.0 pipeline scheduler instead of cron job (details <a target="_blank" href="https://medium.com/@joyzoursky/recent-updates-6264d1e5d42f">here</a>).</p>
</blockquote>
<p>Assume that you have already written some code to generate test reports. Would you like to receive and read the test reports every hour? Of course not. So, we also link <a target="_blank" href="https://docs.skygear.io/guides/cloud-db/basics/js/">Skygear’s free Cloud DB</a> service to store the test result. The system only sends alerts when a test case changes from PASS to FAIL, or FAIL to PASS. This notification approach may vary according to the project need.</p>
<p>To save and retrieve data from the Skygear database, we could use the existing SDK. Or if you are a Python user, you may use this little <a target="_blank" href="https://github.com/skygear-demo/python-db-client">Python DB Client</a> to help write your data handler . We use it to save test results after each test case, and retrieve the reports after running all test suites.</p>
<p>Finally, we can have the test result alerts sent on demand.</p>
<p>P.S. We use <a target="_blank" href="https://slack.com/">Slack</a> <a target="_blank" href="https://api.slack.com/rtm">real time messaging API</a> to do the reporting, so we can receive notifications in the corresponding project channels.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*xHr_ezmVEBYRVZ4oOLJq3Q.png" alt="Image" width="800" height="240" loading="lazy"></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Now, whenever there is a code update on the production branch, this automated UI test is triggered and tests are done automatically. Failure results will be pushed back to our Slack channel to notify our developers.</p>
<p>The biggest barrier to setting up a free automated UI test is probably researching the tools if you are not already a professional QA tester. Since QA is my full-time job, I hope that sharing our upgraded UI Test stack will help free up your time as well!</p>
<p>If you found this useful, please click the ? below so other people can see it too. Thank you!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Setting up CI/CD on GitLab for deploying Python Flask application on Heroku ]]>
                </title>
                <description>
                    <![CDATA[ By Bharath Kallur Recently I came across a challenge to deploy a Python Flask web application to Heroku. The code of the App was hosted in GitLab. Heroku supports deploying an App from GitHub, Dropbox along with the usual Heroku git. It had been quit... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/setting-up-a-ci-cd-on-gitlab-for-deploying-a-python-flask-application-on-heroku-e154db93952b/</link>
                <guid isPermaLink="false">66d45dd79208fb118cc6cf8b</guid>
                
                    <category>
                        <![CDATA[ Flask Framework ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitLab ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 13 Apr 2017 04:31:28 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9cb550740569d1a4cad5db.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Bharath Kallur</p>
<p>Recently I came across a challenge to deploy a Python Flask web application to Heroku. The code of the App was hosted in GitLab.</p>
<p>Heroku supports deploying an App from GitHub, Dropbox along with the usual Heroku git. It had been quite a while since I used Heroku. I was wondering if I can deploy code directly from my GitLab repository instead of using any of the sources mentioned above.</p>
<p>I couldn’t find any information or documentation around deploying applications hosted in a GitLab repository to <a target="_blank" href="https://devcenter.heroku.com/categories/deployment">Heroku</a>. I browsed a bit on GitLab and it turned out that apart from helping test and build your project, GitLab CI can also help <a target="_blank" href="https://docs.gitlab.com/ce/ci/environments.html">deploy</a> you App to your hosting infrastructure. I was now intrigued.</p>
<p>Before I delve into how I deployed the App, I’d like to explain the benefits of using GitLab or GitHub when you can easily get things done with Heroku Git.</p>
<ol>
<li><strong>Easier code maintenance</strong> - With code repository hosting services like GitHub and GitLab, maintenance of code is easy.</li>
<li><strong>Customising pipelines</strong> - With GitLab, we can write our own <a target="_blank" href="http://yaml.org/">yaml</a> file and include the libraries required for running our application.</li>
<li><strong>For better understanding of Continuous Integration and Continuous Development (CI/CD)</strong> - For beginners, this setup helps you understand the coding workflow of testing -&gt; version control -&gt; code maintenance -&gt; application deployment.</li>
</ol>
<p>Here are the steps required to deploy your App hosted in GitLab to Heroku. The steps here assume you already have a good understanding of Python, Flask, version control, GitLab and Heroku. This write up is also helpful for someone who is just starting out. I have kept it as simple as possible to get things up and running.</p>
<h3 id="heading-uploading-the-project-to-gitlab">Uploading the project to GitLab</h3>
<ol>
<li>Create a Python virtual environment for us to use. Get into the virtual environment.</li>
<li>Create a sample Python Flask application on your machine.</li>
<li>Verify that everything is running fine.</li>
<li>Run the command <code>pip freeze &gt; requirements.</code>txt from within the main application folder to catch all the requirements for running your application.</li>
<li>Create a Procfile which is used by Heroku to declare what commands are run by your application on the Heroku platform. Procfile usually consists of the web server used to run the application. In our case let us use Gunicorn, the default Python WSGI HTTP server. The content of your Procfile will be <code>web: gunicorn &lt;name of the app.py file&gt;:&lt;</code>app-name&gt; where app-name is usually “app”. Place this file within the main application folder.</li>
<li>Now login (or signup) to GitLab and create a project. The moment you do this, you will get a standard set of instructions on how to “link” your code on your development machine to GitLab project. Just follow the commands, and after that you can do a git push or git pull to/from this project. This is a bit of an elaborate step and your last statement should look something like <code>git push -u origin master</code>. Once done, on refreshing the project page on GitLab, you should see all your code appear in GitLab.</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ZR0E8T-GwjFxcxSIqt-Hwe75YUHsM70s6kMG" alt="Image" width="800" height="373" loading="lazy">
<em>Project repository in GitLab</em></p>
<h3 id="heading-linking-gitlab-and-heroku">Linking GitLab and Heroku</h3>
<ol>
<li>Login to the <a target="_blank" href="https://www.heroku.com">Heroku web portal</a> and create an application. Give it a nice name and runtime selection.</li>
<li>Now within the my_app folder on your development machine, create a file called “.gitlab-ci.yaml” (note the ‘.’ in the beginning).</li>
<li>This yaml file will have the following structure.</li>
</ol>
<pre><code>my_app_file_name:
 script:
 — apt-get update -qy
 — apt-get install -y python-dev python-pip
 — pip install -r requirements.txt
 — <span class="hljs-keyword">export</span> MONGOHQ_URL=$MONGO_URL

<span class="hljs-attr">production</span>:
 type: deploy
 <span class="hljs-attr">script</span>:
 — apt-get update -qy
 — apt-get install -y ruby-dev
 — gem install dpl
 — dpl — provider=heroku — app=task-mgmt-app — api-key=$HEROKU_SECRET_KEY
 <span class="hljs-attr">only</span>:
 — master
</code></pre><ol>
<li>Change my_app_file_name to the file name of your flask application. You need to set the HEROKU_SECRET_KEY variable in the project variables. You will get this key in the <a target="_blank" href="https://dashboard.heroku.com/">Heroku dashboard</a>. To set it in your GitLab project, go to <strong>Settings &gt; CI/CD Pipelines</strong> and search for <strong>Secret Variables.</strong> While using these variables in the yaml, we need to prepend the variable with the ‘$’ sign. It is a good practice not to share secret keys with anyone and also restrict access to them in the project.</li>
<li>You are almost there. Run the command <code>git add .gitlab-ci.yml</code> and <code>git commit -m &lt;msg&gt;</code> and <code>git push -u origin master</code>. You will see the file in the GitLab repository now.</li>
<li>On the GitLab “My Dashboard” page, click on <strong>Pipelines &gt; Jobs</strong> to see the job which has started running.</li>
<li>In case you are using a database in your App, you might want to link it to the App by placing the details in the .gitlab-ci.yaml file. Please check <a target="_blank" href="https://gitlab.com/bharathkallurs/tasks/blob/master/.gitlab-ci.yml">here</a> for an example. I have used MongDB in my application. Heroku provides adding a set of free libraries/apps to your application. There is an <a target="_blank" href="https://devcenter.heroku.com/articles/mongolab">mLab link</a> for adding MongoDB.</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/C3IGAaKXdfqtg4z1tVXvomMnTo3vQepyewix" alt="Image" width="800" height="394" loading="lazy">
<em>Jobs list in the Pipeline</em></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/p79aifr4r3f4LDcoXJ0qEgUyeIKRnGvYvg16" alt="Image" width="800" height="359" loading="lazy">
_A running job. Screenshot related to my_app_file<em>name in yaml.</em></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ZYTyYC9SiR0klvctSE8HMVEPgdNHm0UwUPt3" alt="Image" width="800" height="465" loading="lazy">
<em>Successful App deploy on Heroku</em></p>
<p>Yay! You have now successfully integrated your GitLab with Heroku with CI/CD configuration. Make all code changes you want in your repository, push it to the GitLab project, and see a job start every time there is a code push. For the current setup, I used GitLab public runners available <a target="_blank" href="http://gitlab.com/ci">here</a>. You can set up a custom GitLab runner and set appropriate configuration.</p>
<h3 id="heading-useful-links">Useful links :</h3>
<ol>
<li><a target="_blank" href="https://devcenter.heroku.com/articles/git#creating-a-heroku-remote">Creating heroku remote</a></li>
<li><a target="_blank" href="https://docs.gitlab.com/ce/ci/examples/test-and-deploy-python-application-to-heroku.html">Setting up CI/CD from GitLab on Heroku</a></li>
<li><a target="_blank" href="https://gitlab.com/bharathkallurs/tasks/tree/master">A task management application - repository : GitLab, deployed on Heroku</a></li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
