<?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" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[RAZINJ Dev]]></title><description><![CDATA[Your Blog for Software Development Ideas and Guides]]></description><link>https://razinj.dev/</link><image><url>https://razinj.dev/favicon.png</url><title>RAZINJ Dev</title><link>https://razinj.dev/</link></image><generator>Ghost 5.53</generator><lastBuildDate>Tue, 23 Jun 2026 18:42:34 GMT</lastBuildDate><atom:link href="https://razinj.dev/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Python Unit Testing: Project Setup]]></title><description><![CDATA[<p>Unit testing is a critical component of modern software development, enabling developers to catch bugs and ensure that their code behaves as expected. If you&apos;re new to unit testing or looking to improve your existing workflow, this guide will help you get started with unit testing in Python.</p>]]></description><link>https://razinj.dev/how-to-add-unit-tests-to-a-python-project/</link><guid isPermaLink="false">64529911c9fe7200011ddbcc</guid><category><![CDATA[Python]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Wed, 03 May 2023 17:30:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2023/05/how-to-add-unit-tests-to-a-python-project.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2023/05/how-to-add-unit-tests-to-a-python-project.png" alt="Python Unit Testing: Project Setup"><p>Unit testing is a critical component of modern software development, enabling developers to catch bugs and ensure that their code behaves as expected. If you&apos;re new to unit testing or looking to improve your existing workflow, this guide will help you get started with unit testing in Python. In this post, we&apos;ll walk through the steps required to configure a Python project for effective unit testing. We&apos;ll cover the tools and libraries needed, how to write and run tests. By the end of this post, you&apos;ll have the knowledge and skills you need to start writing and running unit tests in Python.</p><h2 id="table-of-contents">Table of contents</h2><ul><li>Modules Used for Testing</li><li>Tests Discovery &amp; Tests Naming Conventions</li><li>Commands to Run the Tests</li><li><code>pytest</code> Config File</li><li>Setup Example</li><li>Conclusion</li></ul><h2 id="modules-used-for-testing">Modules Used for Testing</h2><p>There are several modules (packages/libraries) that can be used to write and run your tests with.</p><h3 id="test-frameworks-test-runners">Test frameworks / Test runners</h3><!--kg-card-begin: markdown--><ul>
<li>Python&apos;s built-in module <a href="https://docs.python.org/3/library/unittest.html?ref=razinj.dev"><code>unittest</code></a>:
<ul>
<li>Has built-in assertion and mocking APIs,</li>
<li>Compared to <code>pytest</code> it has a verbose assertion APIs,</li>
<li>No auto-discovery, you need to mark a folder as a module in order to be discovered,</li>
<li>No plugins support.</li>
</ul>
</li>
<li>Standalone module <a href="https://docs.pytest.org/en/7.2.x/?ref=razinj.dev"><code>pytest</code></a> (recommended):
<ul>
<li>Has built-in assertion and mocking APIs,</li>
<li>Simpler assertion APIs compared to <code>unittest</code>,</li>
<li>Auto-discovery of test modules,</li>
<li>Can run <code>unittest</code> tests (great to support legacy tests),</li>
<li>Supports plugins.</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><p>Both are good, but it depends on what you want to test, one module can be a better choice over the other depending on the what you&#x2019;re testing.</p><p>For the sake of being future proof, I recommend going with <code>pytest</code> which is better than <code>unittest</code> when it comes to running tests whilst having a big list of plugins which makes it easier to mock third party modules (in <code>unittest</code> you need to mock a lot to make a third-party module fully mocked).</p><h2 id="tests-discovery-tests-naming-conventions">Tests Discovery &amp; Tests Naming Conventions</h2><p>Test files needs to be discovered in order to be tested (ran), and there is a difference between how <code>unittest</code> and <code>pytest</code> are discovering tests.</p><p>In <code>unittest</code>:</p><ul><li>Files needs to be in the same directory,</li><li>If it&#x2019;s in a nested folder, it needs to be a module (by adding a <code>__init__.py</code> file).</li><li>Files&apos; names needs to start with <code>test_*.py</code> (default),</li><li>You can override this pattern using <code>-p</code> CLI flag.</li><li>Test methods needs to start with the same pattern: <code>test_*(self):</code>,</li><li>Inside each test file, you need to have a main method/function:</li></ul><pre><code class="language-python">if __name__ == &apos;__main__&apos;:
    unittest.main()
</code></pre><ul><li>Tests cases (test grouping) can be done via a class:</li></ul><pre><code class="language-python">class MyFunctionalityTests(unittest.TestCase):
</code></pre><p>In <code>pytest</code> (recommended):</p><ul><li>Files doesn&#x2019;t need to be in the same directory,</li><li>Files&apos; names should be prefixed or suffixed by <code>test_*.py</code> or <code>*_test.py</code>,</li><li>Test methods needs to start with a prefix: <code>test_*():</code> or <code>test_*(self):</code> (if inside a class test case),</li><li>No need for a main method/function,</li><li>Tests cases (test grouping) can be done via a class;</li></ul><pre><code class="language-python">class TestMyFunctionality:
</code></pre><h2 id="commands-to-run-the-tests">Commands to Run the Tests</h2><p>In <code>unittest</code>:</p><pre><code class="language-python">python3 -m unittest file_name.py
</code></pre><p>In <code>pytest</code>:</p><pre><code class="language-python">pytest
</code></pre><p>More ways on how to invoke tests in <code>pytest</code> can be found <a href="https://docs.pytest.org/en/7.1.x/how-to/usage.html?ref=razinj.dev">here</a>.</p><h2 id="pytest-config-file"><code>pytest</code> Config File</h2><p>To set some configurations for <code>pytest</code> runner, you can create a config file with different formats, but for the sake of simplicity use either <code>.pytest.ini</code> or <code>pytest.ini</code>.</p><p>A sample configuration file:</p><pre><code class="language-ini">[pytest]
testpaths = src test
</code></pre><p>More on configuration options can be found <a href="https://docs.pytest.org/en/7.1.x/reference/customize.html?ref=razinj.dev">here</a>.</p><h2 id="setup-example">Setup Example</h2><p>Finally to the setup example, we will start by creating the required files and content of each file:</p><p>Create a directory if you&apos;re setting a new project:</p><pre><code class="language-shell">mkdir my-python-project
</code></pre><p>Create a <code>main.py</code> file:</p><pre><code class="language-shell">touch main.py
</code></pre><pre><code class="language-python">def custom_sum(first_number, second_number):
    print(f&apos;Summing {first_number} and {second_number}&apos;)
    return first_number + second_number

if __name__ == &apos;__main__&apos;:
    print(custom_sum(1, 1))
</code></pre><p>Create a <code>main_test.py</code> file:</p><pre><code class="language-shell">touch main_test.py
</code></pre><pre><code class="language-python">import builtins

from pytest_mock import MockerFixture
from main import custom_sum

class TestCustomSum:
    def test_should_sum_two_positive_numbers_correctly(self, mocker: MockerFixture):
	    # Arrange
        print_spy = mocker.spy(builtins, &apos;print&apos;)

		# Act
        result = custom_sum(1, 1)

		# Assert
        print_spy.assert_called_once_with(&apos;Summing 1 and 1&apos;)
        assert result == 2
</code></pre><p>Create a <code>requirements.txt</code> file (dependency file like <code>package.json</code> in JS/TS based projects):</p><pre><code class="language-shell">touch requirements.txt
</code></pre><pre><code>pytest==7.2.2
pytest-mock==3.10.0
</code></pre><p>Install dependencies:</p><pre><code class="language-shell">python3 -m pip install -r requirements.txt
</code></pre><p>Run tests:</p><pre><code class="language-shell">pytest
</code></pre><h2 id="conclusion">Conclusion</h2><p>In this guide, we&apos;ve covered the essential steps required to configure your Python project for effective unit testing. We started by introducing the modules commonly used for testing in Python and discussed the importance of adhering to naming conventions to make your tests easy to discover and maintain. We then covered the commands you&apos;ll need to run your tests and how to create a <code>pytest</code> configuration file to customize your testing environment. Finally, we provided an example of how to set up your project for testing using the popular <code>pytest</code> framework.</p><p>By following these guidelines, you can ensure that your Python code is thoroughly tested, helping to catch bugs early and ensure that your software behaves as expected. Remember, unit testing is a critical component of modern software development, and investing the time to learn how to do it effectively can pay dividends in the long run.</p><p>You can find the code of this post in this <a href="https://github.com/razinj-dev/python-unit-tests-setup?ref=razinj.dev">repository</a>.</p><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[How to Mock process.env in Jest]]></title><description><![CDATA[<p>Learn how to mock environment variables in a NodeJs project while implementing Jest tests that depend on environmental variables.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F310;</div><div class="kg-callout-text">You can find the source code at the end of the post.</div></div><h2 id="table-of-contents">Table of contents</h2><ul><li>What Is process.env?</li><li>Why Mock process.env?</li><li>Mock process.env in Jest Tests</li><li>Conclusion</li></ul>]]></description><link>https://razinj.dev/how-to-mock-process-env-in-jest/</link><guid isPermaLink="false">63c4520bbe3c9d0001ca3317</guid><category><![CDATA[Web]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Sun, 15 Jan 2023 19:33:44 GMT</pubDate><media:content url="https://razinj.dev/content/images/2023/01/how-to-mock-process-env-in-jest.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2023/01/how-to-mock-process-env-in-jest.png" alt="How to Mock process.env in Jest"><p>Learn how to mock environment variables in a NodeJs project while implementing Jest tests that depend on environmental variables.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F310;</div><div class="kg-callout-text">You can find the source code at the end of the post.</div></div><h2 id="table-of-contents">Table of contents</h2><ul><li>What Is process.env?</li><li>Why Mock process.env?</li><li>Mock process.env in Jest Tests</li><li>Conclusion</li></ul><h2 id="what-is-processenv">What Is process.env?</h2><p>In NodeJs, process.env is basically a global object that holds the state of the system&apos;s environmental variables, being available at runtime to our application.</p><h2 id="why-mock-processenv">Why Mock process.env?</h2><p>Most of the time when developing an application we don&apos;t really need the process.env variable or it&apos;s properties values, but in some parts of the application we do need them, for example if we need to grab the app&apos;s version in runtime or the app&apos;s port to start a server of some kind.</p><p>To make sure that the app behaves the way we want it to, we need to test it thoroughly and this mean we need to test the parts of the app that rely on this object and this is the case where we need to mock process.env to test different cases the app can run into.</p><h2 id="mock-processenv-in-jest-tests">Mock process.env in Jest Tests</h2><p>For the sake of this post, we will have a very simple NodeJs app that has one file with two functions; <code>appEnv</code> and <code>appVersion</code> which will print out the app&apos;s env and version respectively.</p><p>Create a file named <code>app.js</code> and copy the following content into it:</p><pre><code class="language-JavaScript">function appEnv() {
  const currentEnv = process.env.NODE_ENV

  if (!currentEnv) {
    throw new Error(&apos;NODE_ENV is not defined&apos;)
  }

  return `App is running on ${currentEnv} environment`
}

function appVersion() {
  const currentAppVersion = process.env.APP_VERSION

  if (!currentAppVersion) {
    throw new Error(&apos;APP_VERSION is not defined&apos;)
  }

  return `App&apos;s version is ${currentAppVersion}`
}

module.exports = { appEnv, appVersion }

</code></pre><p>As you can see, we only print out the values here, and if the value is undefined we throw an error, pretty simple.</p><p>Now that we got this out of the way, we can start.<br>Mocking <code>process.env</code> is actually pretty simple, but first we need to know where we can set those values for our tests after that we will mock them in our tests.</p><h3 id="app-wide-mocking">App Wide Mocking</h3><p>The default values for every test can be set in one of Jest&apos;s setup files, when this is done, the setup file will be executed before each test file.</p><p>To setup Jest setup-files execute the following steps:</p><p>First, create a <code>jest.config.js</code> file in the root of the directory (besides <code>package.json</code>).<br>Content of the config file would be:</p><pre><code class="language-javascript">module.exports = {
  setupFiles: [&apos;&lt;rootDir&gt;/jest-setup.js&apos;],
}
</code></pre><p>Then, create a <code>setup-jest.js</code> file in the root of the directory too.<br>Content of the file should be our default values:</p><pre><code class="language-javascript">process.env.NODE_ENV = &apos;local&apos;
process.env.APP_VERSION = &apos;v0.1&apos;
</code></pre><p>Now we did setup the default values for the whole app&apos;s tests env, Let&apos;s mock them!</p><p>First, create a test file named <code>app.test.js</code> next to <code>app.js</code> file.<br>Then we can start our tests in a dedicated test suite (<code>App Tests - Default ENV Values</code>) as you can see below:</p><pre><code class="language-JavaScript">const { appEnv, appVersion } = require(&apos;./app&apos;)

describe(&apos;App Tests - Default ENV Values&apos;, () =&gt; {
  const originalEnv = process.env

  afterEach(() =&gt; {
    // Reset process.env after each test case
    process.env = originalEnv
  })

  describe(&apos;APP_VERSION Tests&apos;, () =&gt; {
    it(&apos;should return default value when APP_VERSION&apos;, () =&gt; {
      const result = appVersion()

      expect(result).toBe(`App&apos;s version is v0.1`)
    })

    it(&apos;should throw an error if APP_VERSION is not defined&apos;, () =&gt; {
      process.env = { APP_VERSION: undefined }
      // OR process.env = {}

      expect(appVersion).toThrow(&apos;APP_VERSION is not defined&apos;)
    })
  })

  describe(&apos;NODE_ENV Tests&apos;, () =&gt; {
    it(&apos;should return default value when NODE_ENV&apos;, () =&gt; {
      const result = appEnv()

      expect(result).toBe(&apos;App is running on local environment&apos;)
    })

    it(&apos;should throw an error if NODE_ENV is not defined&apos;, () =&gt; {
      process.env = { NODE_ENV: undefined }
      // OR process.env = {}

      expect(appEnv).toThrow(&apos;NODE_ENV is not defined&apos;)
    })
  })
})
</code></pre><p>Now let&apos;s talk about the above tests, first we created a test group named <code>App Tests - Default ENV Values</code> which contains two other groups <code>APP_VERSION Tests</code> and <code>NODE_ENV Tests</code>.</p><p>Before we dive into the tests, notice we have a variable to hold the original env object&apos;s value, that&apos;s important because in some tests we do override the <code>process.env</code> properties and we need to reset it to the original value after each test case.</p><p>Our first test case, in both groups, ensures that our env variables are correct and picked up from the setup file we had earlier.</p><p>Second test case again in both groups is different, we override the <code>process.env</code> value to set undefined values for our properties (<code>NODE_ENV</code> &amp; <code>APP_VERSION</code>) and those tests should ensure that our code will throw an error when those values are missing from env variable.</p><h3 id="per-test-file-mocking">Per Test File Mocking</h3><p>Now since we mocked the app-wide env variable, let&apos;s find out how to mock the env variables in a test file/suite in case we don&apos;t have a setup file or we just want to test different values other than the values in the setup file.</p><p>In the same <code>app.test.js</code> file, we can add another group named <code>App Tests - Manual ENV Values</code> right next to <code>App Tests - Default ENV Values</code> (just to keep the test file clean and clear).</p><pre><code class="language-JavaScript">describe(&apos;App Tests - Manual ENV Values&apos;, () =&gt; {
  const originalEnv = process.env

  beforeEach(() =&gt; {
    // Override process.env object before each test case:
    process.env = {
      NODE_ENV: &apos;prod&apos;,
      APP_VERSION: &apos;v1.0&apos;,
    }
    // By default the value of process.env.NODE_ENV is test when running tests with jest
  })

  afterEach(() =&gt; {
    // Reset process.env value after each test case
    process.env = originalEnv
  })

  describe(&apos;APP_VERSION Tests&apos;, () =&gt; {
    it(&apos;should return correct value when APP_VERSION is defined&apos;, () =&gt; {
      const result = appVersion()

      expect(result).toBe(`App&apos;s version is v1.0`)
    })

    it(&apos;should throw an error if APP_VERSION is not defined&apos;, () =&gt; {
      process.env = { APP_VERSION: undefined }
      // OR process.env = {}

      expect(appVersion).toThrow(&apos;APP_VERSION is not defined&apos;)
    })
  })

  describe(&apos;NODE_ENV Tests&apos;, () =&gt; {
    it(&apos;should return correct value when NODE_ENV is defined&apos;, () =&gt; {
      const result = appEnv()

      expect(result).toBe(&apos;App is running on prod environment&apos;)
    })

    it(&apos;should throw an error if NODE_ENV is not defined&apos;, () =&gt; {
      process.env = { NODE_ENV: undefined }
      // OR process.env = {}

      expect(appEnv).toThrow(&apos;NODE_ENV is not defined&apos;)
    })
  })
})
</code></pre><p>Again, let&apos;s talk about our new test group.</p><p>Actually, not much changed, In the new test group we mock or set custom values for the <code>process.env</code> object before each test case which allows us to have more control over the values in our test file instead of the globally set variables.</p><h2 id="conclusion">Conclusion</h2><p>At last, in this post we saw how we can set our env variables globally while testing our application along with a per-test mock of the env object.</p><p>Source code can be found <a href="https://github.com/razinj-dev/node-mock-process-env?ref=razinj.dev">here</a>.</p><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[JSON Stringify - The Optional Parameters]]></title><description><![CDATA[<p>We all use <code>JSON.stringify()</code> method for various reasons, but very few know about it&apos;s hidden optional parameters, and in this BitCode we will discover them with examples.</p><h2 id="table-of-contents">Table of contents</h2><ul><li><a href="#normal-usage">Normal Usage</a></li><li><a href="#whitelist-objects-properties">Whitelist Object&apos;s Properties</a></li><li><a href="#replacer-function">Replacer Function</a></li><li><a href="#output-spacing">Output Spacing</a></li></ul><h2 id="normal-usage">Normal Usage</h2><p>Before we start, this</p>]]></description><link>https://razinj.dev/json-stringify-method-the-optional-parameters/</link><guid isPermaLink="false">6373ef8314a4ca0001003c89</guid><category><![CDATA[BitCode]]></category><category><![CDATA[Web]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Tue, 10 Jan 2023 20:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2023/01/json-stringify-method-the-optional-parameters.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2023/01/json-stringify-method-the-optional-parameters.png" alt="JSON Stringify - The Optional Parameters"><p>We all use <code>JSON.stringify()</code> method for various reasons, but very few know about it&apos;s hidden optional parameters, and in this BitCode we will discover them with examples.</p><h2 id="table-of-contents">Table of contents</h2><ul><li><a href="#normal-usage">Normal Usage</a></li><li><a href="#whitelist-objects-properties">Whitelist Object&apos;s Properties</a></li><li><a href="#replacer-function">Replacer Function</a></li><li><a href="#output-spacing">Output Spacing</a></li></ul><h2 id="normal-usage">Normal Usage</h2><p>Before we start, this is just a quick example of a normal usage, we can use it as reference/comparison to what&apos;s coming up next.</p><pre><code class="language-javascript">const user = {
  name: &apos;John&apos;,
  age: 20,
  isSingle: true,
}

const result = JSON.stringify(user)

console.log(result)
// Output: {&quot;name&quot;:&quot;John&quot;,&quot;age&quot;:20,&quot;isSingle&quot;:true}
</code></pre><h2 id="whitelist-objects-properties">Whitelist Object&apos;s Properties</h2><p>We can choose what properties we want to include in the stringified output by passing a list of string values that represents the whitelisted properties.</p><pre><code class="language-javascript">const user = {
  name: &apos;John&apos;,
  age: 20,
  isSingle: true,
}

const result = JSON.stringify(user, [&apos;name&apos;])

console.log(result)
// Output: {&quot;name&quot;:&quot;John&quot;}
</code></pre><h2 id="replacer-function">Replacer Function</h2><p>We can also have a function to format our data before it&apos;s printed, the replacer function has two parameters, a <code>key</code> and a <code>value</code> (self-explanatory).<br>To demonstrate the power of the replacer function, we will add a <code>password</code> field to our <code>user</code> object, and to keep our users safe, we will replace the password value with a static one as follows:</p><pre><code class="language-JavaScript">const user = {
  name: &apos;John&apos;,
  age: 20,
  isSingle: true,
  password: &apos;super_sensitive_password&apos; // Our new field
}

// Replacer function&apos;s name doesn&apos;t matter.
const replacer = (key, value) =&gt; {
    if (key === &apos;password&apos;) {
        return &apos;****&apos;
    }

	return value
}

const result = JSON.stringify(user, replacer)

console.log(result)
// Output: {&quot;name&quot;:&quot;John&quot;,&quot;age&quot;:20,&quot;isSingle&quot;:true,&quot;password&quot;:&quot;****&quot;}
</code></pre><h2 id="output-spacing">Output Spacing</h2><p>Finally, we can make our output more readable by passing a third argument to represent the amount of whitespace to be inserted (could be a string indentation/line break or a number for normal spacing).<br>Also, we don&apos;t have to pass a replacer function to insert some spacing, and this is exactly what we will do (we can pass either an <code>undefined</code> or a <code>null</code> to the second argument).</p><pre><code class="language-JavaScript">const user = {
  name: &apos;John&apos;,
  age: 20,
  isSingle: true,
}

const space = JSON.stringify(user, null, 2)

console.log(space)
/**
 * Output:
 * {
 *   &quot;name&quot;: &quot;John&quot;,
 *   &quot;age&quot;: 20,
 *   &quot;isSingle&quot;: true
 * }
 */

const indentation = JSON.stringify(user, null, &apos;\t&apos;)

console.log(indentation)
/**
 * Output:
 * {
 *  	&quot;name&quot;: &quot;John&quot;,
 *  	&quot;age&quot;: 20,
 *  	&quot;isSingle&quot;: true
 * }
 */
</code></pre><h2 id="conclusion">Conclusion</h2><p>At last, we discovered the lesser known parameters of the <code>JSON.stringify()</code> method.</p><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[How to Use Git Stash Command]]></title><description><![CDATA[Save your work, without committing a crime!]]></description><link>https://razinj.dev/how-to-use-git-stash-command/</link><guid isPermaLink="false">62ffed119766940001d7f873</guid><category><![CDATA[BitCode]]></category><category><![CDATA[Git]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Sat, 20 Aug 2022 20:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/08/learn-git-stash-essentials.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/08/learn-git-stash-essentials.png" alt="How to Use Git Stash Command"><p>Learn how to use Git stash in case you ever wondered how to save your changes without committing them; that&apos;s exactly the point from Git stash, you <em>stash</em> the changes in a dirty working directory.</p><p>In this BitCode you will learn the essentials of Git stash to get going.</p><h2 id="when-to-use-git-stash">When To Use Git Stash?</h2><p>Often when you work on some part of a project, and you would like to switch to work on another thing unrelated in even a different branch, you would like to save your changes without committing them; maybe because they are undone or needs some refactoring.</p><p>This is where Git stash is super useful, now let&apos;s dive into the essential commands.</p><h3 id="create-a-git-stash">Create a Git Stash</h3><p>To create a stash, there are two commands to do it.</p><p>Both <code>git stash</code>/<code>git stash push</code>; will create an entry holding the current </p><p><strong>Note:</strong> Keep in mind that Git stash will only hold changes from already tracked files and/or staged changes; meaning if you just created a file, you will have to stage it first before being able to stash it.</p><p>Creating a stash is awesome, but it stores the stashes with a naming like <code>WIP on <em>branchname</em></code><em> </em>which is not ideal if you have a lot of stashes.</p><p>So to create a stash with a message, you can use the following command:</p><pre><code class="language-SHELL">git stash push -m &quot;A message so I can remember what this stash is about&quot;</code></pre><p>Like this, you won&apos;t forget what the stash is about, and you will be able to apply a stash to the working directory with ease from the list.</p><h3 id="list-retrieve-git-stashes">List &amp; Retrieve Git Stashes</h3><p>Now we will see how to list Git stashes and <em>apply</em> them into our working directory.</p><p>To list the stashes:</p><pre><code class="language-SHELL">git stash list

# Should print something like this:
# stash@{0}: On master: Another stash for a new feature
# stash@{1}: On master: A stash with a message</code></pre><p>To retrieve a stash:</p><p>Before we start retrieving the stashes, we need to know the commands used and the difference between them.</p><p>The commands are: <code>git stash apply</code> and <code>git stash pop</code> </p><p>The difference between them is that <code>apply</code> applies the stash to the working directory and keeps the stash entry intact, while <code>pop</code> applies the stash and removes the stash entry from the list.</p><p>Now that this is cleared out, let&apos;s dig into it:</p><pre><code># Apply the most recent stash:
git stash apply # or git stash pop

# Apply a specific stash by it&apos;s index
git stash apply &lt;index&gt; # or git stash pop &lt;index&gt;</code></pre><p>Keep in mind that while applying a stash you can encounter a conflict, this is normal you just have to solve it as any other merge conflict.</p><p>In the case of using <code>pop</code> the stash is not removed, so after solving the conflict you should remove the stash manually by using <code>drop</code> which is next in the post.</p><h3 id="drop-a-stash-or-clear-all-stashes">Drop a Stash or Clear All Stashes</h3><p>As you can create, you can delete, and you have the choice to delete a specific stash or clear out the list.</p><p>To delete a single stash:</p><pre><code class="language-SHELL">git stash drop &lt;index&gt;</code></pre><p>To clear out the whole list:</p><pre><code class="language-SHELL">git stash clear</code></pre><h2 id="conclusion">Conclusion</h2><p>Git has a lot under it sleeves, and Git stash is one of those things I wish I learned since day one.</p><h2 id="sources">Sources</h2><ul><li>Git <a href="https://git-scm.com/docs/git-stash?ref=razinj.dev">docs</a></li><li>Git book <a href="https://git-scm.com/book/en/v2/Git-Tools-Stashing-and-Cleaning?ref=razinj.dev">docs</a></li></ul>]]></content:encoded></item><item><title><![CDATA[How to Setup Redux in React Native]]></title><description><![CDATA[Redux, Redux Persist, Async Storage and React Native]]></description><link>https://razinj.dev/how-to-setup-redux-in-react-native/</link><guid isPermaLink="false">62d5a80d11aa900001c459a8</guid><category><![CDATA[Mobile]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Wed, 20 Jul 2022 20:03:18 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/07/how-to-setup-redux-in-react-native.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/07/how-to-setup-redux-in-react-native.png" alt="How to Setup Redux in React Native"><p>Setting up Redux with persistence in local storage in a web React application is fairly easy, but in React Native, It&apos;s a slightly different process and tools.</p><p>In this post, we will see how to set up Redux with storage persistence in React Native.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F310;</div><div class="kg-callout-text">You can find the source code at the end of the post.</div></div><h2 id="table-of-contents">Table of Contents</h2><ul><li><a href="#requirements">Requirements</a></li><li><a href="#start-a-react-native-project">Start a React Native Project</a></li><li><a href="#create-a-counter-component">Create a Counter Component</a></li><li><a href="#install-redux-and-create-a-store">Install Redux and Create a Store</a></li><li><a href="#create-counter-slice">Create Counter Slice</a></li><li><a href="#use-redux-in-counter-component">Use Redux in Counter Component</a></li><li><a href="#add-store-persistence">Add Store Persistence</a></li><li><a href="#conclusion">Conclusion</a></li><li><a href="#sources">Sources</a></li></ul><h2 id="requirements">Requirements</h2><ul><li>Basic React and React Native knowledge</li><li>Basic Redux knowledge</li><li>Basic Typescript knowledge</li></ul><h2 id="start-a-react-native-project">Start a React Native project</h2><p>Obviously we need a project to work on, if you don&apos;t have a project for this, start from here.</p><p>Create a project with Typescript template:</p><pre><code class="language-SHELL">npx react-native init react_native_redux --template react-native-template-typescript</code></pre><p>This command creates a project with the following file structure:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/07/react-native-redux-file-structure.png" class="kg-image" alt="How to Setup Redux in React Native" loading="lazy" width="504" height="952"></figure><p>Now let&apos;s move on to create some visuals.</p><h2 id="create-a-counter-component">Create a Counter component</h2><p>To demonstrates and test the Redux installation we will need to create a component for that, I chose a counter for its simplicity:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/07/react-native-redux-counter-component-code.png" class="kg-image" alt="How to Setup Redux in React Native" loading="lazy" width="1156" height="1658" srcset="https://razinj.dev/content/images/size/w600/2022/07/react-native-redux-counter-component-code.png 600w, https://razinj.dev/content/images/size/w1000/2022/07/react-native-redux-counter-component-code.png 1000w, https://razinj.dev/content/images/2022/07/react-native-redux-counter-component-code.png 1156w" sizes="(min-width: 720px) 720px"></figure><p>Visually, it should look like this:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/07/react-native-redux-counter-component-ui.png" class="kg-image" alt="How to Setup Redux in React Native" loading="lazy" width="940" height="330" srcset="https://razinj.dev/content/images/size/w600/2022/07/react-native-redux-counter-component-ui.png 600w, https://razinj.dev/content/images/2022/07/react-native-redux-counter-component-ui.png 940w" sizes="(min-width: 720px) 720px"></figure><p>Of course, you can run the app now with:</p><pre><code class="language-SHELL">npm run android</code></pre><h2 id="install-redux-and-create-a-store">Install Redux and create a Store</h2><p>Now we can install Redux, in this step we will be installing Redux Toolkit and React-Redux bindings packages:</p><pre><code class="language-SHELL">npm i react-redux @reduxjs/toolkit</code></pre><p>Now for the store, create a file in the root of the project named <code>store.ts</code> and paste the following code in it:</p><pre><code class="language-Typescript">import {configureStore} from &apos;@reduxjs/toolkit&apos;;

export const store = configureStore({
  reducer: {},
});

export type RootState = ReturnType&lt;typeof store.getState&gt;;
export type AppDispatch = typeof store.dispatch;</code></pre><p>This is an empty Store, we will populate it later on.</p><p>One last step is to wrap our main component with the store provider:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/07/react-native-redux-provider.png" class="kg-image" alt="How to Setup Redux in React Native" loading="lazy" width="1302" height="834" srcset="https://razinj.dev/content/images/size/w600/2022/07/react-native-redux-provider.png 600w, https://razinj.dev/content/images/size/w1000/2022/07/react-native-redux-provider.png 1000w, https://razinj.dev/content/images/2022/07/react-native-redux-provider.png 1302w" sizes="(min-width: 720px) 720px"></figure><h2 id="create-counter-slice">Create Counter Slice</h2><p>Now it&apos;s time to integrate our Counter logic with Redux using the Slice file:</p><p>Create a file in the root project named <code>counterSlice.ts</code> with the following code:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/07/react-native-redux-counter-slice.png" class="kg-image" alt="How to Setup Redux in React Native" loading="lazy" width="1352" height="1156" srcset="https://razinj.dev/content/images/size/w600/2022/07/react-native-redux-counter-slice.png 600w, https://razinj.dev/content/images/size/w1000/2022/07/react-native-redux-counter-slice.png 1000w, https://razinj.dev/content/images/2022/07/react-native-redux-counter-slice.png 1352w" sizes="(min-width: 720px) 720px"></figure><p>This file will export actions, selector and the reducer.</p><p>We also need to update the store with our reducer, like so:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/07/react-native-redux-updated.png" class="kg-image" alt="How to Setup Redux in React Native" loading="lazy" width="1004" height="528" srcset="https://razinj.dev/content/images/size/w600/2022/07/react-native-redux-updated.png 600w, https://razinj.dev/content/images/size/w1000/2022/07/react-native-redux-updated.png 1000w, https://razinj.dev/content/images/2022/07/react-native-redux-updated.png 1004w" sizes="(min-width: 720px) 720px"></figure><p>That&apos;s enough for now, let&apos;s move on to our Counter component.</p><h2 id="use-redux-in-counter-component">Use Redux in Counter component</h2><p>After we created our Slice file for the counter, we can update our component to use those actions.</p><p>We will use the selector to get the counter value and dispatch the exported actions to update it.</p><p>Have the component updated like so:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/07/react-native-redux-counter-component-redux-updated.png" class="kg-image" alt="How to Setup Redux in React Native" loading="lazy" width="1292" height="1476" srcset="https://razinj.dev/content/images/size/w600/2022/07/react-native-redux-counter-component-redux-updated.png 600w, https://razinj.dev/content/images/size/w1000/2022/07/react-native-redux-counter-component-redux-updated.png 1000w, https://razinj.dev/content/images/2022/07/react-native-redux-counter-component-redux-updated.png 1292w" sizes="(min-width: 720px) 720px"></figure><p>After adding the changes shown above, you can run the app and test it, it should run as if we are still using the <code>useState</code> hook.</p><p>But there is something we didn&apos;t add to our project, which is persistence, we will cover this in the next step.</p><h2 id="add-store-persistence">Add Store &#xA0;Persistence</h2><p>Now it&apos;s time to persist our store, but in React Native we are talking about a mobile device, so there is no local storage, in this case, we will be using two packages; Redux Persist to persist and rehydrate our store and Async Storage as storage adapter.</p><p>Let&apos;s start by installing the packages:</p><pre><code class="language-SHELL">npm i redux-persist @react-native-async-storage/async-storage</code></pre><p>Now let&apos;s add persistence to our application:</p><p>In the Store file, we will have some changes:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/07/react-native-redux-store-redux-persist-update.png" class="kg-image" alt="How to Setup Redux in React Native" loading="lazy" width="1244" height="1550" srcset="https://razinj.dev/content/images/size/w600/2022/07/react-native-redux-store-redux-persist-update.png 600w, https://razinj.dev/content/images/size/w1000/2022/07/react-native-redux-store-redux-persist-update.png 1000w, https://razinj.dev/content/images/2022/07/react-native-redux-store-redux-persist-update.png 1244w" sizes="(min-width: 720px) 720px"></figure><p>And we also need to wrap our main component with the PersistGate like so:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/07/react-native-redux-app-redux-persist-update.png" class="kg-image" alt="How to Setup Redux in React Native" loading="lazy" width="1314" height="938" srcset="https://razinj.dev/content/images/size/w600/2022/07/react-native-redux-app-redux-persist-update.png 600w, https://razinj.dev/content/images/size/w1000/2022/07/react-native-redux-app-redux-persist-update.png 1000w, https://razinj.dev/content/images/2022/07/react-native-redux-app-redux-persist-update.png 1314w" sizes="(min-width: 720px) 720px"></figure><p>And now we should have our store persisted, you can test this by missing around with the counter, close the app, re-open it, and you should see the latest value persisted.</p><h2 id="conclusion">Conclusion</h2><p>No doubt, it takes some effort to setup Redux in React Native, but it&apos;s totally worth it.</p><h2 id="sources">Sources</h2><ul><li>React Native <a href="https://reactnative.dev/docs/environment-setup?ref=razinj.dev">development environment setup</a>.</li><li>Redux Toolkit <a href="https://redux-toolkit.js.org/tutorials/quick-start?ref=razinj.dev">documentation</a>.</li><li><a href="https://github.com/rt2zz/redux-persist?ref=razinj.dev">Redux Persist</a></li><li><a href="https://react-native-async-storage.github.io/async-storage/?ref=razinj.dev">Async Storage</a></li></ul><p>Source code can be found <a href="https://github.com/razinj/react_native_redux?ref=razinj.dev">here</a>.</p><hr><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[How to Implement Dark Mode in React Using Context]]></title><description><![CDATA[Dark Mode in React & TailwindCSS]]></description><link>https://razinj.dev/how-to-add-dark-mode-in-react-context/</link><guid isPermaLink="false">627fdb5d604ccd00011d23a4</guid><category><![CDATA[Web]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Sat, 14 May 2022 18:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/05/how-to-add-dark-mode-in-react-context.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/05/how-to-add-dark-mode-in-react-context.png" alt="How to Implement Dark Mode in React Using Context"><p>Dark mode is everywhere nowadays, and probably one of the best things in a website (at least for us, the developers), Together in this post we will learn how to implement dark mode in a React application using TailwindCSS for styles and React&apos;s Context API for data passing and theme switching.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F310;</div><div class="kg-callout-text">You can find the source code at the end of the post.</div></div><h2 id="table-of-contents">Table of Contents</h2><ul><li><a href="#requirements">Requirements</a></li><li><a href="#why-use-tailwindcss">Why Use TailwindCSS?</a></li><li><a href="#why-use-context-api">Why Use Context API?</a></li><li><a href="#create-the-project">Create the Project</a></li><li><a href="#install-dependencies-and-setup">Install Dependencies and Setup</a></li><li><a href="#create-components">Create Components</a></li><li><a href="#create-theme-context-and-wrapper">Create Theme Context and Wrapper</a></li><li><a href="#tweak-themeswitch-component">Tweak ThemeSwitch Component</a></li><li><a href="#conclusion">Conclusion</a></li><li><a href="#sources">Sources</a></li></ul><h2 id="requirements">Requirements</h2><ul><li>Basic understanding of React and Typescript</li></ul><h2 id="why-use-tailwindcss">Why Use TailwindCSS?</h2><p>The main reason I&apos;m using TailwindCSS is it&apos;s ease of use, especially when it comes to dark mode.</p><p>We will be using the <code>class</code> strategy instead of the <code>media</code> strategy to manually trigger dark mode.</p><h2 id="why-use-context-api">Why Use Context API?</h2><p>We will be using the Context API for two reasons, the first is to pass data down to whatever child component we want (the one responsible for theme switching) and the second is to update the theme.</p><h2 id="create-the-project">Create the Project</h2><p>Before the darkness, we need a project, the project used in this post is generated with <a href="https://create-react-app.dev/?ref=razinj.dev">Create React App</a> using the Typescript template.</p><p>Run the following command to generate a project:</p><pre><code class="language-SHELL">npx create-react-app react-dark-mode-context --template typescript</code></pre><p>Feel free to change the name of the project.</p><h2 id="install-dependencies-and-setup">Install Dependencies and Setup</h2><p>For the project dependencies, we don&apos;t have much &#x2013; it&apos;s just TailwindCSS because Context API is a built-in API/Hook in React.</p><p>Install TailwindCSS:</p><pre><code class="language-SHELL">npm i -D tailwindcss postcss autoprefixer</code></pre><p>And initialize project&apos;s configuration:</p><pre><code class="language-SHELL">npx tailwindcss init</code></pre><p>Now you will have two files generated, we need to tweak the Tailwind config file to include our <code>src</code> and to specify the dark mode strategy:</p><p>The config file should look something like this:</p><pre><code class="language-JAVASCRIPT">module.exports = {
  content: [&apos;./src/**/*.{js,jsx,ts,tsx}&apos;],
  theme: {
    extend: {},
  },
  plugins: [],
  darkMode: &apos;class&apos;,
}
</code></pre><p>Now add the Tailwind directives to our root styles file (It&apos;s the <code>index.css</code> file) :</p><pre><code class="language-CSS">@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre><p>That&apos;s it for the installation and setup, let&apos;s move on.</p><h2 id="create-components">Create Components</h2><p>For the sake of this post, let&apos;s keep things as simple as we can.</p><p>For that reason, below you will see we have two components (not including the App component).</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/05/react-dark-mode-context-components.svg" class="kg-image" alt="How to Implement Dark Mode in React Using Context" loading="lazy" width="422" height="341"></figure><p>So, let&apos;s create these components (order doesn&apos;t matter, but it will be easier if you follow the below order):</p><p>Create a <code>components</code> folder in the <code>src</code> folder, then create a file for each component (except the <code>App.tsx</code> file, it&apos;s already there).</p><p>The <code>ThemeSwitch</code> component:</p><pre><code class="language-TYPESCRIPT">import React from &apos;react&apos;

const ThemeSwitch = () =&gt; {
  return (
    &lt;button
      style={{
        padding: 5,
        borderRadius: 5,
        color: &apos;black&apos;,
        background: &apos;white&apos;,
      }}
    &gt;
      Go DARK MODE
    &lt;/button&gt;
  )
}

export default ThemeSwitch</code></pre><p>The <code>MainComponent</code> component:</p><pre><code class="language-TYPESCRIPT">import { FC } from &apos;react&apos;
import logo from &apos;../logo.svg&apos;
import ThemeSwitch from &apos;./ThemeSwitch&apos;

const MainComponent: FC = () =&gt; {
  return (
    &lt;div className=&apos;font-sans flex flex-col justify-center items-center h-screen dark:bg-zinc-700 dark:text-white&apos;&gt;
      &lt;img src={logo} width={200} alt=&apos;React Logo&apos; /&gt;
      &lt;h1 className=&apos;text-2xl&apos;&gt;Hello World!&lt;/h1&gt;
      &lt;h2&gt;React + TailwindCSS Dark Mode App&lt;/h2&gt;
      &lt;div className=&apos;mt-2&apos;&gt;
        &lt;ThemeSwitch /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}

export default MainComponent</code></pre><p>Note that I added some dark mode styles in the component using the <code>dark:</code> keyword, we will see the effect once we implement the theme context and switching.</p><p>The <code>App</code> component:</p><pre><code class="language-TYPESCRIPT">import { FC } from &apos;react&apos;
import MainComponent from &apos;./components/MainComponent&apos;

const App: FC = () =&gt; {
  return &lt;MainComponent /&gt;
}

export default App</code></pre><p>All good, let&apos;s implement the theme switching using the Context API.</p><h2 id="create-theme-context-and-wrapper">Create Theme Context and Wrapper</h2><p>Now we are in the exciting part, the Context API &#x2013; in this section we will create two files, one for the theme context and the other for the wrapper, you might ask why we need a wrapper? It&apos;s just to make the App component as clear and clean as possible.</p><p>Create a <code>context</code> folder in the <code>src</code> folder.</p><p>The <code>ThemeContext</code> file:</p><pre><code class="language-TYPESCRIPT">import { createContext } from &apos;react&apos;

const defaultValue = {
  currentTheme: &apos;light&apos;,
  changeCurrentTheme: (newTheme: &apos;light&apos; | &apos;dark&apos;) =&gt; {},
}

const ThemeContext = createContext(defaultValue)

export default ThemeContext</code></pre><p>This file has the context, and it&apos;s default value, <code>currentTheme</code> is self-explanatory and the method <code>changeCurrentTheme</code> is the method responsible for theme switching (we will write its implementation/content in the wrapper).</p><p>The <code>ThemeContextWrapper</code> component:</p><pre><code class="language-TYPESCRIPT">import { useState, useEffect, FC, ReactNode } from &apos;react&apos;
import ThemeContext from &apos;./ThemeContext&apos;

const ThemeContextWrapper: FC&lt;{ children: ReactNode }&gt; = ({ children }) =&gt; {
  const [theme, setTheme] = useState(localStorage.getItem(&apos;theme&apos;) || &apos;light&apos;)

  const changeCurrentTheme = (newTheme: &apos;light&apos; | &apos;dark&apos;) =&gt; {
    setTheme(newTheme)
    localStorage.setItem(&apos;theme&apos;, newTheme)
  }

  useEffect(() =&gt; {
    if (theme === &apos;light&apos;) document.body.classList.remove(&apos;dark&apos;)
    else document.body.classList.add(&apos;dark&apos;)
  }, [theme])

  return &lt;ThemeContext.Provider value={{ currentTheme: theme, changeCurrentTheme }}&gt;{children}&lt;/ThemeContext.Provider&gt;
}

export default ThemeContextWrapper
</code></pre><p>This is the real deal here, the core of operations &#x2013; let me explain what happens here.</p><p>We need to make it clear that Context&apos;s values are being accessible for child components if they are wrapper around a provider (what we return in this case), this explains why we have <code>children</code> as props to this component, inside the component we have a simple state for the current theme also the implementation of the previous method we discussed that changes the theme.</p><p>At first render, the component checks for the current value stored in local storage (yes, this project is preference-capable, surprise!) if the value is not found then we fall-back to light mode, all this happens in the <code>useState</code> hook at first.</p><p>Then based on that state the <code>useEffect</code> is triggered to set the suitable class (which is basically adding pr removing the <code>dark</code> class from the document body, remeber we said we are using the Tailwind <code>class</code> strategy? Well, this is it).</p><p>The <code>useEffect</code> will run every time the <code>theme</code> value from the state changes, which happens when the <code>changeCurrentTheme</code> function is called, we update the state&apos;s value with the new theme selected and persist this value in local storage.</p><p>That&apos;s it, it&apos;s this simple.</p><h2 id="tweak-themeswitch-component">Tweak ThemeSwitch Component</h2><p>There is one thing we need to change in the <code>ThemeSwitch</code> component, we need to use the <code>ThemeContext</code> values and capabilities.</p><p>Open up that component and edit like below:</p><pre><code class="language-TYPESCRIPT">import React from &apos;react&apos;
import ThemeContext from &apos;../context/ThemeContext&apos;

const ThemeSwitch = () =&gt; {
  const { currentTheme, changeCurrentTheme } = React.useContext(ThemeContext)

  return (
    &lt;button
      data-testid=&apos;switch-theme-btn&apos;
      style={{
        padding: 5,
        borderRadius: 5,
        color: currentTheme === &apos;light&apos; ? &apos;white&apos; : &apos;black&apos;,
        background: currentTheme === &apos;light&apos; ? &apos;black&apos; : &apos;white&apos;,
      }}
      onClick={() =&gt; changeCurrentTheme(currentTheme === &apos;light&apos; ? &apos;dark&apos; : &apos;light&apos;)}
    &gt;
      Go {currentTheme === &apos;light&apos; ? &apos;DARK MODE&apos; : &apos;LIGHT MODE&apos;}
    &lt;/button&gt;
  )
}

export default ThemeSwitch</code></pre><p>You can notice that we are accessing the current theme value and the method to switch themes using the Context hook.</p><p>The current theme value is used to determine the styles in the component, as well as the value passed to the method when being called after clicking the switching button.</p><p>Now, that&apos;s it for real, we have a dark mode enabled React application.</p><h2 id="conclusion">Conclusion</h2><p>As we saw, in simple steps we implemented dark mode in a React app, now it&apos;s up to you to change the styles using Tailwind <code>dark:</code> keyword to make the necessary changes for a dark environment.</p><p><a href="https://razinj-react-dark-mode-context.netlify.app/?ref=razinj.dev">Live demo</a></p><h2 id="sources">Sources</h2><ul><li><a href="https://create-react-app.dev/?ref=razinj.dev">Create React App</a></li><li>React <a href="https://reactjs.org/docs/context.html?ref=razinj.dev">Context</a></li><li>TailwindCSS <a href="https://tailwindcss.com/docs/installation/using-postcss?ref=razinj.dev">installation</a> and <a href="https://tailwindcss.com/docs/dark-mode?ref=razinj.dev">dark mode</a></li></ul><p>Source code can be found <a href="https://github.com/razinj/react-dark-mode-context?ref=razinj.dev">here</a>.</p><hr><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[How to Create an IT Blog Post]]></title><description><![CDATA[The Whole Process in an Image]]></description><link>https://razinj.dev/how-to-create-a-blog-post/</link><guid isPermaLink="false">62782708604ccd00011d2337</guid><category><![CDATA[BitCode]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Sun, 08 May 2022 20:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/05/how-to-create-a-blog-post-flowchart-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/05/how-to-create-a-blog-post-flowchart-1.png" alt="How to Create an IT Blog Post"><p>Hey folks, Sense I started this blog I occasionally get asked about the process to create a blog post, so in this BitCode I will share my process to create and publish an IT blog post.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F4D6;</div><div class="kg-callout-text">You can find a downloadable PDF at the end of the post.</div></div><h2 id="flowchart">Flowchart</h2><p>Below is a Flowchart for the post creation process:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://razinj.dev/content/images/2022/05/razinj-dev-blog-post-process-flowchart-1.svg" class="kg-image" alt="How to Create an IT Blog Post" loading="lazy" width="651" height="1322"><figcaption>Blog Post Process Flowchart</figcaption></figure><h2 id="download-pdf">Download PDF</h2>
        <div class="kg-card kg-file-card ">
            <a class="kg-file-card-container" href="https://razinj.dev/content/files/2022/05/Blog-Post-Process-Flowchart.pdf" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">Blog Post Process Flowchart</div>
                    <div class="kg-file-card-caption">Get your PDF copy of the process shown above</div>
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">Blog Post Process Flowchart.pdf</div>
                        <div class="kg-file-card-filesize">23 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        ]]></content:encoded></item><item><title><![CDATA[Javascript's Rest Parameters]]></title><description><![CDATA[Rest Parameters, Explained!]]></description><link>https://razinj.dev/javascript-rest-parameters/</link><guid isPermaLink="false">6272d86f1caf7d0001f4926d</guid><category><![CDATA[BitCode]]></category><category><![CDATA[Web]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Wed, 04 May 2022 20:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/05/javascript-rest-parameters-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/05/javascript-rest-parameters-1.png" alt="Javascript&apos;s Rest Parameters"><p>JavaScript&apos;s functions with the Rest parameters (also commonly called Rest Operator) are simply JavaScript&apos;s ways to implement <a href="https://en.wikipedia.org/wiki/Variadic_function?ref=razinj.dev">variadic functions</a>, and in this BitCode we will take a quick look into it along with some examples.</p><p>It allows a function to accept an unlimited number of arguments that will be available as a list inside the function.</p><h2 id="the-syntax">The Syntax</h2><p>The Rest parameters syntax is a sequence of three dots <code>...</code> followed by an argument name, all inside the parentheses of a function.</p><p>Please do not confuse Rest Parameters with the Spread Syntax, take a look at this <a href="https://razinj.dev/javascript-spread-syntax/">BitCode</a> or click the link below to know more about it.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://razinj.dev/javascript-spread-syntax/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Javascript&#x2019;s Spread Syntax</div><div class="kg-bookmark-description">What is the Spread Syntax and its common uses in Javascript ES6.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://razinj.dev/favicon.png" alt="Javascript&apos;s Rest Parameters"><span class="kg-bookmark-author">RAZINJ Dev</span><span class="kg-bookmark-publisher">Nizar Jailane</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://razinj.dev/content/images/2022/04/javascript-spread-syntax-1.png" alt="Javascript&apos;s Rest Parameters"></div></a></figure><h2 id="quick-notes">Quick Notes</h2><p>Some notes to write down before start using the Rest parameter.</p><ul><li>A function can only have one Rest parameter,</li><li>The Rest parameter should be the last parameter in the function definition.</li></ul><p>After clearing these points, let&apos;s take a look at some examples.</p><h2 id="examples">Examples</h2><p>Let&apos;s see a couple of examples using the Rest parameter.</p><ul><li>Find the max of undefined number of items</li></ul><pre><code class="language-Javascript">const findMax = (...numbers) =&gt; {
    return Math.max(...numbers)
}

console.log(findMax(1, 2, 4, 100, 410, 0))
// Output: 410</code></pre><ul><li>Get the sum of a bunch of number</li></ul><pre><code>const sum = (...numbers) =&gt; {
    return numbers.reduce((x, y) =&gt; x + y, 0)
}

console.log(sum(10, 2, 3, 89))
// Output: 104</code></pre>]]></content:encoded></item><item><title><![CDATA[Javascript's Spread Syntax]]></title><description><![CDATA[Spread Syntax, Explained!]]></description><link>https://razinj.dev/javascript-spread-syntax/</link><guid isPermaLink="false">6269c168e6a021000168cc66</guid><category><![CDATA[BitCode]]></category><category><![CDATA[Web]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Thu, 28 Apr 2022 21:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/05/javascript-spread-syntax.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/05/javascript-spread-syntax.png" alt="Javascript&apos;s Spread Syntax"><p>JavaScript introduced a bunch of new features in ES6, one of them is the Spread Syntax (also commonly called Spread Operator), and in this BitCode we will have a look at it, and its common uses.</p><h2 id="the-syntax">The Syntax</h2><p>The spread syntax is a sequence of three dots <code>...</code>, pretty simple, the syntax allows an iterable to be expanded in other places.</p><p>The same syntax has another use in JavaScript&apos;s functions, you can learn more in this <a href="https://razinj.dev/javascript-rest-parameters/">BitCode</a>, or by clicking the link below.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://razinj.dev/javascript-rest-parameters/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Javascript&#x2019;s Rest Parameters</div><div class="kg-bookmark-description">What are the Rest Parameters in Javascript along with some usage examples.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://razinj.dev/favicon.png" alt="Javascript&apos;s Spread Syntax"><span class="kg-bookmark-author">RAZINJ Dev</span><span class="kg-bookmark-publisher">Nizar Jailane</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://razinj.dev/content/images/2022/05/javascript-rest-parameters.png" alt="Javascript&apos;s Spread Syntax"></div></a></figure><h2 id="common-uses">Common Uses</h2><p>There are a lot of common uses for it, let&apos;s take a look into them one by one.</p><h2 id="concatenate-arrays-and-objects">Concatenate Arrays and Objects</h2><p>The spread syntax allows us to concatenate multiple lists or objects in a simple syntax, Let&apos;s see the code.</p><p>Using arrays:</p><pre><code class="language-Javascript">const arr_1 = [1, 2, 3]
const arr_2 = [4, 5, 6]

const all_arrays = [ ...arr_1, ...arr_2 ]

console.log(all_arrays)
// Output: [ 1, 2, 3, 4, 5, 6 ]</code></pre><p>Using objects:</p><pre><code class="language-Javascript">const obj_1 = { foo: &apos;foo_value&apos; }
const obj_2 = { bar: &apos;bar_value&apos; }

const object_with_all_keys = { ...obj_1, ...obj_2 }

console.log(object_with_all_keys)
// Output: { foo: &apos;foo_value&apos;, bar: &apos;bar_value&apos; }</code></pre><h2 id="deep-copy-reference-change">Deep Copy (Reference Change)</h2><p>The spread syntax can also be used to make a deep copy for arrays and objects that changes the memory reference (the same result can be achieved with ES5&apos;s <code>Object.assign</code>).</p><p>To better see the change the spread syntax provides in this regard, let&apos;s see a typical copy and its behavior:</p><pre><code class="language-Javascript">const obj_1 = { foo: &apos;foo_value&apos; }
const obj_2 = obj_1

obj_1.foo = &apos;foo_value_updated&apos;

console.log(obj_1, obj_2)
// Output: { foo: &apos;foo_value_updated&apos; } { foo: &apos;foo_value_updated&apos; }</code></pre><p>As we can see, after we updated the value of <code>foo</code> in <code>obj_1</code> the same change is reflected in <code>obj_2</code> and this is unwanted behavior.</p><p>Now let&apos;s fix this by using the spread syntax:</p><pre><code class="language-Javascript">const obj_1 = { foo: &apos;foo_value&apos; }
const obj_2 = { ... obj_1 }

obj_1.foo = &apos;foo_value_updated&apos;

console.log(obj_1, obj_2)
// Output: { foo: &apos;foo_value_updated&apos; } { foo: &apos;foo_value&apos; }</code></pre><p>Magic, three dots solved an issue.</p><p>PS: Deep copy (clone) is not going to work with nested data structure (nested array or object).</p><h2 id="spread-strings">Spread Strings</h2><p>Another use is spreading a string into a list of characters.</p><pre><code>const a_string = &apos;Hello World!&apos;;

const list_of_chars = [ ...a_string ]

console.log(list_of_chars)
// Output: [&apos;H&apos;, &apos;e&apos;, &apos;l&apos;, &apos;l&apos;, &apos;o&apos;, &apos; &apos;, &apos;W&apos;, &apos;o&apos;, &apos;r&apos;, &apos;l&apos;, &apos;d&apos;, &apos;!&apos;]</code></pre>]]></content:encoded></item><item><title><![CDATA[How to Self Host a Docker Registry]]></title><description><![CDATA[Now Unlimited and Private]]></description><link>https://razinj.dev/how-to-self-host-a-docker-registry/</link><guid isPermaLink="false">623f2ed5e6a021000168ca5e</guid><category><![CDATA[Self Hosted]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Docker Compose]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Sat, 26 Mar 2022 17:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/03/how-to-self-host-a-docker-registry-cover.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/03/how-to-self-host-a-docker-registry-cover.png" alt="How to Self Host a Docker Registry"><p><a href="https://docs.docker.com/registry?ref=razinj.dev">Docker Registry</a> is a registry for Docker images that is used by <a href="https://hub.docker.com/?ref=razinj.dev">Docker Hub</a>, but the problem with Docker Hub is that we get only one private repository for our image and in case we need multiple private images self hosting is a Docker registry in our existing server is the best option, and in this post, we will be doing just that.</p><h2 id="table-of-contents">Table of contents</h2><ul><li><a href="https://razinj.dev/how-to-self-host-a-docker-registry/#requirements">Requirements</a></li><li><a href="https://razinj.dev/how-to-self-host-a-docker-registry/#notes-in-self-hosting-a-registry">Notes in self-hosting a registry</a></li><li><a href="https://razinj.dev/how-to-self-host-a-docker-registry/#install-with-docker-compose">Install with Docker Compose</a></li><li><a href="https://razinj.dev/how-to-self-host-a-docker-registry/#use-nginx-as-a-proxy">Use NGINX as a proxy</a></li><li><a href="https://razinj.dev/how-to-self-host-a-docker-registry/#conclusion">Conclusion</a></li><li><a href="https://razinj.dev/how-to-self-host-a-docker-registry/#sources">Sources</a></li></ul><h2 id="requirements">Requirements</h2><ul><li><a href="https://docs.docker.com/get-docker?ref=razinj.dev">Docker</a></li><li><a href="https://docs.docker.com/compose/install?ref=razinj.dev">Docker Compose</a></li><li>Basic understanding of Docker and Docker Compose</li><li><a href="https://www.nginx.com/?ref=razinj.dev">NGINX</a></li></ul><h2 id="notes-in-self-hosting-a-registry">Notes in self-hosting a registry</h2><p>There are two notes about self-hosting your own Docker Registry in production that I would like you to know.</p><ul><li>Using Docker Registry in a production environment requires a secure connection (SSL/TLS).</li><li>It&apos;s best to proxy the registry through NGINX web server for more control and security.</li></ul><p>After taking notes, we can start installing the registry.</p><h2 id="install-with-docker-compose">Install with Docker Compose</h2><p>We will be installing the registry using Docker Compose, for ease of use.</p><p>Create a <code>docker-compose.yml</code> file:</p><pre><code class="language-SHELL">touch docker-compose.yml</code></pre><p>Paste the following content into the file:</p><figure class="kg-card kg-code-card"><pre><code class="language-YAML">version: &apos;3&apos;

services:
  app:
    image: registry:2
    container_name: docker-registry
    restart: unless-stopped
    environment:
      REGISTRY_AUTH: htpasswd
      REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
      REGISTRY_AUTH_HTPASSWD_REALM: Local Registry Realm
    ports:
      - 127.0.0.1:8000:5000
    volumes:
      - ./data:/var/lib/registry
      - ./auth:/auth:ro</code></pre><figcaption>docker-compose.yml</figcaption></figure><p>In this file we describe our registry, using the local port <code>8000</code> and local volumes for our data and authentication (we will get into this below).</p><p>Since we specified our auth method to <code>htpasswd</code> (basic auth) we need to generate <code>htpasswd</code> file using this command:</p><pre><code class="language-SHELL">docker run --entrypoint htpasswd httpd:2 -Bbn my_registry_user my_registry_password &gt; htpasswd</code></pre><p>This will create a file named <code>htpasswd</code> with content similar to this:</p><pre><code class="language-SHELL"># cat htpasswd
my_registry_user:$2y$05$mexS70Ju7VkXag5X7EzDI.Mrt5JZPU5K3mh1oPL0oydhTj2HMpUk.</code></pre><p>Now to make this work with our <code>docker-compose.yml</code> file we need to create the <code>auth</code> directory and move the file into it like this:</p><pre><code class="language-SHELL">mkdir auth &amp;&amp; mv htpasswd auth</code></pre><p>Our file tree should look like this:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/03/docker-registry-docker-compose-file-tree.png" class="kg-image" alt="How to Self Host a Docker Registry" loading="lazy" width="500" height="182"></figure><p>We are now done with the installation preparation, let&apos;s run the registry now:</p><pre><code class="language-SHELL">docker-compose up -d</code></pre><p>Check the logs to see if there are any errors:</p><pre><code class="language-SHELL">docker-compose logs -f</code></pre><p>If it&apos;s all good, move on to the next step.</p><h2 id="use-nginx-as-a-proxy">Use NGINX as a proxy</h2><p>In order to expose the registry to the internet, we will be using NGINX as a proxy web server.</p><p>For a specific domain or subdomain, we can use the following configuration:</p><figure class="kg-card kg-code-card"><pre><code class="language-CONF">upstream docker_registry_app {
    server 127.0.0.1:8000;
}

server {
    listen 443 ssl http2;
    server_name sub.domain.com;

    # ssl
    ssl_certificate /etc/nginx/certificates/domain.com.pem;
    ssl_certificate_key /etc/nginx/certificates/domain.com.key;

    # logging
    access_log /var/log/nginx/sub.domain.com.access.log;
    error_log /var/log/nginx/sub.domain.com.error.log warn;

    client_max_body_size 0;

    # reverse proxy
    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_read_timeout 900;

        proxy_pass http://docker_registry_app;
    }
}

# https www redirect
server {
    listen 443 ssl;
    server_name www.sub.domain.com;

    # ssl
    ssl_certificate /etc/nginx/certificates/domain.com.pem;
    ssl_certificate_key /etc/nginx/certificates/domain.com.key;

    return 301 https://sub.domain.com$request_uri;
}

# http and www redirect
server {
    listen 80;
    server_name sub.domain.com www.sub.domain.com;

    return 301 https://sub.domain.com$request_uri;
}</code></pre><figcaption>sub.domain.com.conf</figcaption></figure><p>Please note that <code>client_max_body_size 0;</code> means there is no limit to the body size, feel free to adapt the value to your needs.</p><p>After adding or changing the NGINX config, restart with:</p><pre><code class="language-SHELL">sudo nginx -t &amp;&amp; sudo nginx -s reload</code></pre><p>Now, you can test if everything is working fine by visiting the following URL in your browser:</p><pre><code>https://sub.domain.com/v2/_catalog</code></pre><p>You will be asked to enter your basic auth credentials (the username and password you used to generate the <code>htpassed</code> file earlier).</p><h2 id="conclusion">Conclusion</h2><p>Like this, I can say we are successfully self-hosting our own private Docker Registry ready for production use.</p><h2 id="sources">Sources</h2><ul><li>Docker Registry <a href="https://docs.docker.com/registry/?ref=razinj.dev">website</a>.</li><li>Docker Registry <a href="https://docs.docker.com/registry/spec/api/?ref=razinj.dev">HTTP API specs</a>.</li></ul><hr><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[How to Run Strapi 4 in a Docker Container Using Docker Compose]]></title><description><![CDATA[The Latest Version in a Container]]></description><link>https://razinj.dev/how-to-run-strapi-4-in-a-docker-container-using-docker-compose/</link><guid isPermaLink="false">6237346e603fe9000130a2a4</guid><category><![CDATA[Web]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Docker Compose]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Fri, 04 Feb 2022 19:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/03/how-to-run-strapi-4-in-a-docker-container-using-docker-compose.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/03/how-to-run-strapi-4-in-a-docker-container-using-docker-compose.png" alt="How to Run Strapi 4 in a Docker Container Using Docker Compose"><p>Strapi 4 is released and it&apos;s awesome, but the Docker image is not yet released, and it&apos;s not known when they will release it for us, but this won&apos;t stop us from running Strapi in Docker and that&apos;s what we will be covering in this post.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F310;</div><div class="kg-callout-text">You can find the source code at the end of the post.</div></div><h2 id="table-of-contents">Table of contents</h2><ul><li><a href="https://razinj.dev/how-to-run-strapi-4-in-a-docker-container-using-docker-compose/#requirements">Requirements</a></li><li><a href="https://razinj.dev/how-to-run-strapi-4-in-a-docker-container-using-docker-compose/#create-the-strapi-project">Create the Strapi Project</a></li><li><a href="https://razinj.dev/how-to-run-strapi-4-in-a-docker-container-using-docker-compose/#add-docker-support">Add Docker Support</a></li><li><a href="https://razinj.dev/how-to-run-strapi-4-in-a-docker-container-using-docker-compose/#docker-support-caveat">Docker Support Caveat</a></li><li><a href="https://razinj.dev/how-to-run-strapi-4-in-a-docker-container-using-docker-compose/#run-and-verify-project">Run and Verify Project</a></li><li><a href="https://razinj.dev/how-to-run-strapi-4-in-a-docker-container-using-docker-compose/#content-types-development">Content Types Development</a></li><li><a href="https://razinj.dev/how-to-run-strapi-4-in-a-docker-container-using-docker-compose/#conclusion">Conclusion</a></li></ul><h2 id="requirements">Requirements</h2><ul><li><a href="https://nodejs.org/en/?ref=razinj.dev">NodeJs</a></li><li><a href="https://docs.docker.com/get-docker?ref=razinj.dev">Docker</a></li><li><a href="https://docs.docker.com/compose/install?ref=razinj.dev">Docker Compose</a></li></ul><h2 id="create-the-strapi-project">Create the Strapi Project</h2><p>We need to create a Strapi project named <code>app</code> (I will explain why this name at a later point) using <code>npx</code>:</p><pre><code class="language-SHELL">npx create-strapi-app@latest app</code></pre><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/03/create-strapi-app-with-postgres-database.png" class="kg-image" alt="How to Run Strapi 4 in a Docker Container Using Docker Compose" loading="lazy" width="1332" height="1206" srcset="https://razinj.dev/content/images/size/w600/2022/03/create-strapi-app-with-postgres-database.png 600w, https://razinj.dev/content/images/size/w1000/2022/03/create-strapi-app-with-postgres-database.png 1000w, https://razinj.dev/content/images/2022/03/create-strapi-app-with-postgres-database.png 1332w" sizes="(min-width: 720px) 720px"></figure><p>The command will generate a project with the following file architecture:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/03/strapi-project-file-structure.png" class="kg-image" alt="How to Run Strapi 4 in a Docker Container Using Docker Compose" loading="lazy" width="528" height="636"></figure><h2 id="add-docker-support">Add Docker Support</h2><p>Now we will add Docker support using a <code>Dockerfile</code>.</p><p>In this step, the <code>Dockerfile</code> file needs to be created in the root of the Strapi project, after creating the file copy the following content into it:</p><figure class="kg-card kg-code-card"><pre><code class="language-Dockerfile">FROM strapi/base

# Let WatchTower know to ignore this container for checking
LABEL com.centurylinklabs.watchtower.enable=&quot;false&quot;

WORKDIR /app

COPY ./package*.json ./

RUN npm ci

COPY . .

ENV NODE_ENV production

RUN npm run build

EXPOSE 1337

CMD [&quot;npm&quot;, &quot;start&quot;]
</code></pre><figcaption>Dockerfile</figcaption></figure><p>The <code>Dockerfile</code> will install Strapi&apos;s dependencies and build the project in production mode while exposing the port <code>1337</code>.</p><p>To further optimize the Docker image that will be produced by this <code>Dockerfile</code> we skip copying some files and directories that won&apos;t be used inside the container.</p><p>Ignoring files and directories can be achieved by using a special file called <code>.dockerignore</code> (similar in nature to <code>.gitignore</code>).</p><p>Create the Docker ignore file in the root directory of the project, and copy the following content to it:</p><figure class="kg-card kg-code-card"><pre><code class="language-TXT">node_modules
license.txt
exports
*.cache
build
.strapi-updater.json</code></pre><figcaption>.dockerignore</figcaption></figure><p>So far so good, we added Docker support to our project, but we are not done yet.</p><h2 id="docker-support-caveat">Docker Support Caveat</h2><p>There is a caveat when building Strapi in a Docker container, Strapi uses <code>webpack</code> to build the admin view (which happens when running <code>npm run build</code>).</p><p>To make this <code>Dockerfile</code> effective, we need to supply this build dependency via our <code>package.json</code> file, to do this install <code>webpack</code> as a development dependency:</p><pre><code class="language-SHELL">npm i -D webpack</code></pre><p>With that done, now, we need to create a <code>docker-compose.yml</code> file, which needs to be outside of the root directory of the Strapi project.</p><p>So we need to wrap the project and the <code>docker-compose.yml</code> in a directory (that&apos;s why I called the project <code>app</code> above), let&apos;s do this:</p><pre><code class="language-SHELL"># Step out of the Strapi project named &apos;app&apos;
cd ..

# Create a new directory
mkdir strapi-v4

# Move the Strapi project into the newly created directory
mv app strapi-v4</code></pre><p>Now after wrapping the project in a directory, step into the directory and create the <code>docker-compose.yml</code> file:</p><pre><code class="language-SHELL"># Step into the wrapper directory
cd strapi-v4

# Create an empty file
touch docker-compose.yml</code></pre><p>Now, paste the following content into the file:</p><figure class="kg-card kg-code-card"><pre><code class="language-YAML">version: &quot;3&quot;

services:
  app:
    build:
      context: ./app
    container_name: strapi_v4_app
    restart: unless-stopped
    environment:
      NODE_ENV: production
      DATABASE_CLIENT: postgres
      DATABASE_HOST: db
      DATABASE_PORT: 5432
      DATABASE_NAME: app
      DATABASE_USERNAME: db_username
      DATABASE_PASSWORD: db_password
    volumes:
      - ./app:/srv/app
    ports:
      - 127.0.0.1:8000:1337
    depends_on:
      - db

  db:
    image: postgres:13
    container_name: strapi_v4_db
    restart: unless-stopped
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: db_username
      POSTGRES_PASSWORD: db_password
    volumes:
      - ./data:/var/lib/postgresql/data</code></pre><figcaption>docker-compose.yml</figcaption></figure><p>Let me explain the file for you, the Docker Compose file contains two services; the Strapi project we just created named <code>app</code> (it can be named anything) and a database service that the project will connect to.</p><h2 id="run-and-verify-project">Run and Verify Project</h2><p>Now, we can create our Docker containers and enjoy our CMS, let&apos;s do it:</p><p>Build and run:</p><pre><code class="language-SHELL">docker-compose up -d --build</code></pre><p>Verify that both services are running:</p><pre><code class="language-SHELL">docker ps -a</code></pre><p>The expected output would be something similar to this:</p><figure class="kg-card kg-image-card"><img src="https://razinj.dev/content/images/2022/03/strapi-app-and-postgres-database-docker-containers.png" class="kg-image" alt="How to Run Strapi 4 in a Docker Container Using Docker Compose" loading="lazy" width="1840" height="202" srcset="https://razinj.dev/content/images/size/w600/2022/03/strapi-app-and-postgres-database-docker-containers.png 600w, https://razinj.dev/content/images/size/w1000/2022/03/strapi-app-and-postgres-database-docker-containers.png 1000w, https://razinj.dev/content/images/size/w1600/2022/03/strapi-app-and-postgres-database-docker-containers.png 1600w, https://razinj.dev/content/images/2022/03/strapi-app-and-postgres-database-docker-containers.png 1840w" sizes="(min-width: 720px) 720px"></figure><p>After it finishes you can go to your browser and get into: <code>http://localhost:8000/admin</code> to create an admin profile, and basically, this is it.</p><p>But since this build is a production build, you can&apos;t create types, you will need to create them in the development environment, and that is what&apos;s next.</p><h2 id="content-types-development">Content Types Development</h2><p>In this section, we will see how to create our content types and update the previously built production environment.</p><p>Let&apos;s start with how it works, first whatever content type you create from the UI translates into code inside of the Strapi project and that&apos;s something that can&apos;t be done in the production environment.</p><p>Now that&apos;s cleared out, let&apos;s launch the development environment and create a content type then update the production build.</p><p>First, we need to launch our local database for development, but before that let&apos;s create an extension file for the database to expose the database port:</p><p>Right next to the original <code>docker-compose.yml</code> create a new file:</p><pre><code class="language-SHELL">touch docker-compose.dev.yml</code></pre><p>And paste the following content into it:</p><figure class="kg-card kg-code-card"><pre><code class="language-YAML">version: &quot;3&quot;

services:
  db:
    ports:
      - 5432:5432</code></pre><figcaption>docker-compose.dev.yml</figcaption></figure><p>Now launch the database only specifying both files:</p><pre><code class="language-SHELL"># In the strapi-v4 directory
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d db</code></pre><p>Verify that&apos;s the database is created and that it exposes the port we specified:</p><pre><code class="language-SHELL">docker ps -a</code></pre><p>Second, we need to launch the Strapi project from within the project&apos;s root directory using <code>npm</code>:</p><pre><code class="language-SHELL"># Get into the Strapi project directory
cd app

# Start development server
npm run develop</code></pre><p>Like this, the project will connect to the database using the credentials supplied in the <code>database.js</code> file.</p><p>Now using your browser head into the admin panel at <code>http://localhost:1337/admin</code> and start creating your content types.</p><p>As soon as you create a content type, you will notice that the code in the project&apos;s directory changed (some files are created).</p><p>We are almost done, we need to update the production environment (which is not the one in your local machine), to do this just launch our previous command:</p><pre><code class="language-SHELL">docker-compose up -d --build</code></pre><p>This will re-build the project using the production environment and you can start using the CMS.</p><h2 id="conclusion">Conclusion</h2><p>Like this, we can say that we built our custom Strapi 4 docker image and containers with ease.</p><p>Source code can be found <a href="https://github.com/razinj/strapi-docker-starter?ref=razinj.dev">here</a>.</p><hr><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html--><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Get Docker Images and Containers Updates Notifications]]></title><description><![CDATA[You Won’t Miss an Update From Now On!]]></description><link>https://razinj.dev/docker-images-and-containers-update-notifications/</link><guid isPermaLink="false">6237346e603fe9000130a2a3</guid><category><![CDATA[Docker]]></category><category><![CDATA[Docker Compose]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Wed, 24 Nov 2021 19:29:38 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/03/get-docker-images-and-containers-updates-notifications.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/03/get-docker-images-and-containers-updates-notifications.png" alt="Get Docker Images and Containers Updates Notifications"><p>As a developer or sysadmin running a number of Docker containers, it is critical that you keep them updated regularly when a new release or a security patch is out for your images.</p><p>Being informed is not easy, If you go by checking the Docker image homepage or its releases page, that&apos;s not practical and a waste of time.</p><p>In this post, we will see two ways to get notified about newly released Docker images.</p><h2 id="table-of-contents">Table of contents</h2><ul><li><a href="https://razinj.dev/docker-images-and-containers-update-notifications/#requirements">Requirements</a></li><li><a href="https://razinj.dev/docker-images-and-containers-update-notifications/#the-idea">The Idea</a></li><li><a href="https://razinj.dev/docker-images-and-containers-update-notifications/#watchtower">WatchTower</a></li><li><a href="https://razinj.dev/docker-images-and-containers-update-notifications/#watchtower-start-and-verification">WatchTower Start and Verification</a></li><li><a href="https://razinj.dev/docker-images-and-containers-update-notifications/#bonus-tool-diun">Bonus Tool, Diun</a></li><li><a href="https://razinj.dev/docker-images-and-containers-update-notifications/#conclusion">Conclusion</a></li><li><a href="https://razinj.dev/docker-images-and-containers-update-notifications/#sources">Sources</a></li></ul><h2 id="requirements">Requirements</h2><ul><li><a href="https://docs.docker.com/get-docker?ref=razinj.dev">Docker</a></li><li><a href="https://docs.docker.com/compose/install?ref=razinj.dev">Docker Compose</a></li><li>Basic understanding of Docker and Docker Compose</li></ul><h2 id="the-idea">The Idea</h2><p>You would want something to check the image latest version with the version you are currently running, automatically!, If a newer version is found, you should be notified, then you can examine the change-log and act upon that (you do check the change-log right?)</p><p>For this we have a tool for the job and a bonus one at the bottom, Let&apos;s discover them.</p><h2 id="watchtower">WatchTower</h2><p><a href="https://github.com/containrrr/watchtower?ref=razinj.dev">WatchTower</a> is not only notifying you about new releases but also updates the running containers for you, however, this is not the ideal output we want since sometimes the new release can be a breaking one or needs some kind of preparation ahead of deploying it.</p><p>Let&apos;s see how we can make it work.</p><p>WatchTower works also as a Docker container, for that we will be writing a Docker Compose file to set our configuration for it.</p><p>Create a <code>docker-compose.yml</code> file and paste the following into it:</p><figure class="kg-card kg-code-card"><pre><code class="language-YAML">version: &quot;2&quot;

services:
  app:
    image: containrrr/watchtower
    hostname: SERVER_NAME
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - TZ=Africa/Casablanca
      - NO_COLOR=true
      - WATCHTOWER_SCHEDULE=0 0 19 * * *
      - WATCHTOWER_INCLUDE_STOPPED=true
      - WATCHTOWER_INCLUDE_RESTARTING=true
      - WATCHTOWER_MONITOR_ONLY=true
      - WATCHTOWER_NOTIFICATIONS=gotify
      - WATCHTOWER_NOTIFICATION_GOTIFY_URL=http://192.168.1.197:8001/
      - WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN=A0e2ekUPRRiNhu_
      - WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY=true</code></pre><figcaption>docker-compose.yml</figcaption></figure><p>Before spinning the container up, we will go throw the environment variables used there:</p><ul><li><code>WATCHTOWER_SCHEDULE</code> is where you define when WatchTower will look for updates.</li><li><code>WATCHTOWER_INCLUDE_RESTARTING</code> and <code>WATCHTOWER_INCLUDE_STOPPED</code> are to tell WatchTower to look for updates for stopped and restarting containers as well.</li><li><code>WATCHTOWER_MONITOR_ONLY</code> used to tell WatchTower to not update (this is where notifications only part is).</li><li><code>WATCHTOWER_NOTIFICATIONS</code> this is where you define the notifications channels where possible values are <code>email</code>, <code>slack</code>, <code>msteams</code>, <code>shoutrrr</code>, and <code>gotify</code> (<code>gotify</code> is what we are using in this example, feel free to use whatever notification channel you want or need, we have a <a href="https://razinj.dev/how-to-install-gotify-docker-compose">post</a> on how to install it).</li><li><code>WATCHTOWER_NOTIFICATION_GOTIFY_URL</code>, <code>WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN</code> and <code>WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY</code> are used to define the <code>Gotify</code> server information.</li></ul><p>The other options are up to you to adapt like <code>TZ</code> (TimeZone), <code>NO_COLOR</code>, and docker-compose argument <code>hostname</code> so you can identify from where the notifications are coming.</p><h2 id="watchtower-start-and-verification">WatchTower Start and Verification</h2><p>Start the container up with:</p><pre><code class="language-SHELL">docker-compose up -d</code></pre><p>Check the logs for any errors thrown with:</p><pre><code class="language-SHELL">docker-compose logs</code></pre><p>You should see something similar to the following):</p><pre><code class="language-SHELL">Attaching to watchtower_app_1
app_1  | time=&quot;2021-11-22T21:37:54+01:00&quot; level=warning msg=&quot;Using an HTTP url for Gotify is insecure&quot;
app_1  | time=&quot;2021-11-22T21:37:56+01:00&quot; level=info msg=&quot;Watchtower 1.3.0\nUsing notifications: gotify\nChecking all containers (except explicitly disabled with label)\nScheduling first run: 2021-11-23 19:00:00 +0100 +01\nNote that the first check will be performed in 21 hours, 22 minutes, 3 seconds&quot;</code></pre><p>After checking the logs, in <code>Gotify</code> you should see a notification already carrying almost the same information:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://razinj.dev/content/images/2022/03/watchtower-gotify-startup-notification.png" class="kg-image" alt="Get Docker Images and Containers Updates Notifications" loading="lazy" width="900" height="246" srcset="https://razinj.dev/content/images/size/w600/2022/03/watchtower-gotify-startup-notification.png 600w, https://razinj.dev/content/images/2022/03/watchtower-gotify-startup-notification.png 900w" sizes="(min-width: 720px) 720px"><figcaption>Gotify notification from the web user interface</figcaption></figure><p>If the above checks, we can say the setup was successful, now every day at 19:00 you will receive a notification if there is a container that needs to be updated so you can take action.</p><p>Here is a sample notification (where <code>M0</code>, <code>M1</code> and <code>CM1</code> are names of servers I maintain):</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://razinj.dev/content/images/2022/03/watchtower-for-docker-image-update-notifications-sample.png" class="kg-image" alt="Get Docker Images and Containers Updates Notifications" loading="lazy" width="1764" height="1218" srcset="https://razinj.dev/content/images/size/w600/2022/03/watchtower-for-docker-image-update-notifications-sample.png 600w, https://razinj.dev/content/images/size/w1000/2022/03/watchtower-for-docker-image-update-notifications-sample.png 1000w, https://razinj.dev/content/images/size/w1600/2022/03/watchtower-for-docker-image-update-notifications-sample.png 1600w, https://razinj.dev/content/images/2022/03/watchtower-for-docker-image-update-notifications-sample.png 1764w" sizes="(min-width: 720px) 720px"><figcaption>WatchTower&apos;s notifications sample</figcaption></figure><h2 id="bonus-tool-diun">Bonus Tool, Diun</h2><p><a href="https://github.com/crazy-max/diun?ref=razinj.dev">Diun</a> (<strong>D</strong>ocker <strong>I</strong>mage <strong>U</strong>pdate <strong>N</strong>otifier), is a similar service, actually, it was built for the purpose of notifications only (unlike WatchTower, which can do both; notify and/or update).</p><p>We will see below how you can deploy and use it, then you can be the judge and use what you feel comfortable with.</p><p>To install it, you will do the same thing as WatchTower, create a <code>docker-compose.yml</code> file and paste the following into it:</p><pre><code class="language-YAML">version: &quot;3.5&quot;

services:
  diun:
    image: crazymax/diun:latest
    container_name: diun
    restart: unless-stopped
    command: serve
    volumes:
      - ./data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - TZ=Africa/Casablanca
      - LOG_LEVEL=info
      - LOG_JSON=false
      - DIUN_WATCH_WORKERS=20
      - DIUN_WATCH_SCHEDULE=0 19 * * *
      - DIUN_PROVIDERS_DOCKER=true
      - DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true
      - DIUN_PROVIDERS_DOCKER_WATCHSTOPPED=true
      - DIUN_NOTIF_GOTIFY_ENDPOINT=http://192.168.1.197:8001/
      - DIUN_NOTIF_GOTIFY_TOKEN=AVcPmg-d9sE-Z8s</code></pre><p>Feel free to make the required changes to make it work, like changing the <code>gotify</code> notification information and credentials along with the schedule and timezone.</p><p>Launch it with:</p><pre><code class="language-SHELL">docker-compose up -d</code></pre><p>Below is a sample of Diun&apos;s notifications:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://razinj.dev/content/images/2022/03/diun-for-docker-image-update-notifications-sample.png" class="kg-image" alt="Get Docker Images and Containers Updates Notifications" loading="lazy" width="1446" height="1140" srcset="https://razinj.dev/content/images/size/w600/2022/03/diun-for-docker-image-update-notifications-sample.png 600w, https://razinj.dev/content/images/size/w1000/2022/03/diun-for-docker-image-update-notifications-sample.png 1000w, https://razinj.dev/content/images/2022/03/diun-for-docker-image-update-notifications-sample.png 1446w" sizes="(min-width: 720px) 720px"><figcaption>Diun&apos;s notifications sample</figcaption></figure><h2 id="conclusion">Conclusion</h2><p>Personally, I like how WatchTower delivers notifications with a list of all updates available for each server I have while Diun is sending a notification for each update available.</p><p>But it&apos;s up to you to decide and choose between the two!</p><h2 id="sources">Sources</h2><ul><li>WatchTower <a href="https://github.com/containrrr/watchtower?ref=razinj.dev">source code</a> and <a href="https://containrrr.dev/watchtower?ref=razinj.dev">website</a></li><li>Diun <a href="https://github.com/crazy-max/diun?ref=razinj.dev">source code</a> and <a href="https://crazymax.dev/diun?ref=razinj.dev">website</a></li></ul><hr><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[How to Setup Multiple Git Accounts in the Same Machine]]></title><description><![CDATA[Separate Your Personal and Work Projects]]></description><link>https://razinj.dev/setup-multiple-git-accounts/</link><guid isPermaLink="false">6237346e603fe9000130a2a2</guid><category><![CDATA[Git]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Sat, 02 Oct 2021 14:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/03/how-to-setup-multiple-git-accounts-in-the-same-machine.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/03/how-to-setup-multiple-git-accounts-in-the-same-machine.png" alt="How to Setup Multiple Git Accounts in the Same Machine"><p>Most of the developers are using Git as their version control system and sometimes it comes in handy to have Git set up and configured for multiple accounts for example work and personal configs and accounts.</p><p>In this post, we will see why and how to set up multiple configurations for a seamless workflow.</p><h2 id="tables-of-contents">Tables of contents:</h2><ul><li><a href="https://razinj.dev/setup-multiple-git-accounts/#requirements">Requirements</a></li><li><a href="https://razinj.dev/setup-multiple-git-accounts/#why">Why?</a></li><li><a href="https://razinj.dev/setup-multiple-git-accounts/#how">How?</a></li><li><a href="https://razinj.dev/setup-multiple-git-accounts/#conclusion">Conclusion</a></li></ul><h2 id="requirements">Requirements</h2><ul><li><a href="https://git-scm.com/book/en/v2/Getting-Started-Installing-Git?ref=razinj.dev">Git</a> installed and basic understanding</li></ul><h2 id="why">Why?</h2><p>The main use case for this kind of setup is when you have a single machine on which you develop and code on work and personal projects where you usually don&apos;t have the same Git accounts for both of them.</p><h2 id="how">How?</h2><p>You can start by installing Git if you haven&apos;t already, after that we will be creating two files for each account/config, by default none of these files are created.</p><p>Let&apos;s start by checking them (run command in your home directory <code>~</code>):</p><pre><code class="language-SHELL">cd ~ &amp;&amp; ls -la | grep .gitconfig</code></pre><p>The above command should output no files.</p><p>I will be assuming that you have the following file structure for work and personal projects, but you can adapt as needed.</p><pre><code class="language-TXT">/home/nizar/
&#x2514;&#x2500;&#x2500;&#x2500;personal-projects
&#x2502;   &#x2514;&#x2500;&#x2500;&#x2500;my-cool-project
&#x2502;       &#x2502;   index.html
&#x2502;       &#x2502;   ...
&#x2514;&#x2500;&#x2500;&#x2500;work-projects
&#x2502;   &#x2514;&#x2500;&#x2500;&#x2500;my-professional-project
&#x2502;       &#x2502;   index.html
&#x2502;       &#x2502;   ...</code></pre><p>Now let&apos;s set our global config, this will create a file for the personal account:</p><pre><code class="language-SHELL">git config --global user.name &quot;nizar&quot;
git config --global user.email &quot;nizar-personal@email.com&quot;</code></pre><p>Let&apos;s check the personal config:</p><pre><code class="language-SHELL">git config -l</code></pre><p>You should be able to see what we have just set above.</p><p>Now let&apos;s do a little demo to verify our personal configuration:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://razinj.dev/content/images/2022/03/git-personal-config-demo.png" class="kg-image" alt="How to Setup Multiple Git Accounts in the Same Machine" loading="lazy" width="1594" height="1062" srcset="https://razinj.dev/content/images/size/w600/2022/03/git-personal-config-demo.png 600w, https://razinj.dev/content/images/size/w1000/2022/03/git-personal-config-demo.png 1000w, https://razinj.dev/content/images/2022/03/git-personal-config-demo.png 1594w" sizes="(min-width: 720px) 720px"><figcaption>Git personal config demo</figcaption></figure><p>As you can see I&apos;ve created a directory for my personal project and initialized a Git repository within a project folder, looking at the Git log we can my personal info being applied.</p><p>Now since this is sorted out, Let&apos;s configure our work/professional account, but before we start I want you to take a look at the root directory for a file called <code>.gitconfig</code>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://razinj.dev/content/images/2022/03/git-global-config.png" class="kg-image" alt="How to Setup Multiple Git Accounts in the Same Machine" loading="lazy" width="998" height="304" srcset="https://razinj.dev/content/images/size/w600/2022/03/git-global-config.png 600w, https://razinj.dev/content/images/2022/03/git-global-config.png 998w" sizes="(min-width: 720px) 720px"><figcaption>Git global configuration file</figcaption></figure><p>We will be modifying this file and be creating a new similar one.</p><p>Modify the <code>.gitconfig</code> file:</p><pre><code class="language-Git">[user]
	name = nizar
	email = nizar-personal@email.com

[includeIf &quot;gitdir:/home/nizar/work-projects/&quot;]
	path = /home/nizar/.gitconfig-work</code></pre><p>As you noticed we added an <code>IF</code> statement, telling Git if it is in our <code>work-projects</code> directory (which we will be creating next) use the <code>.gitconfig-work</code> configuration instead of the global one.</p><p>Create the <code>work-projects</code> directory:</p><pre><code class="language-SHELL">mkdir /home/nizar/work-projects</code></pre><p>Create the <code>.gitconfig-work</code> file next to the old one:</p><pre><code class="language-SHELL">nano /home/nizar/.gitconfig-work</code></pre><p>The content will be similar in structure, adapt as needed:</p><pre><code class="language-Git">[user]
        name = nizar-work-name
        email = nizar-work@email.com</code></pre><p>Again, let&apos;s do a demo of this work account/configuration:</p><p><strong>Note:</strong> user and host names are a bit different, it was a mistake on my part, but the image should represent the point without a problem.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://razinj.dev/content/images/2022/07/2022-07-30--20-14-03.png" class="kg-image" alt="How to Setup Multiple Git Accounts in the Same Machine" loading="lazy" width="1384" height="948" srcset="https://razinj.dev/content/images/size/w600/2022/07/2022-07-30--20-14-03.png 600w, https://razinj.dev/content/images/size/w1000/2022/07/2022-07-30--20-14-03.png 1000w, https://razinj.dev/content/images/2022/07/2022-07-30--20-14-03.png 1384w" sizes="(min-width: 720px) 720px"><figcaption>Git work configuration demo</figcaption></figure><p>As you can see, the work configuration is applied in this directory instead of the global personal configuration.</p><h2 id="conclusion">Conclusion</h2><p>Applying the above steps, it&apos;s safe to say that we are done with our multiple Git accounts setups.</p><hr><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Learn and Implement Linux Server Security Best Practices]]></title><description><![CDATA[Learn How to Secure and Harden Your Linux Server]]></description><link>https://razinj.dev/linux-server-hardening-best-practices/</link><guid isPermaLink="false">6237346e603fe9000130a29e</guid><category><![CDATA[Linux]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Sun, 15 Aug 2021 19:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/03/learn-and-implement-linux-server-security-best-practices.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/03/learn-and-implement-linux-server-security-best-practices.png" alt="Learn and Implement Linux Server Security Best Practices"><p>Having an exposed server in the wild is scary where people try to get into our servers and start abusing them.</p><p>In this post, we will cover the must-do procedures to harden and secure our Linux servers.</p><h2 id="table-of-contents">Table of contents</h2><ul><li><a href="https://razinj.dev/linux-server-hardening-best-practices/#requirements">Requirements</a></li><li><a href="https://razinj.dev/linux-server-hardening-best-practices/#packages-updates">Packages Updates</a></li><li><a href="https://razinj.dev/linux-server-hardening-best-practices/#users">Users</a></li><li><a href="https://razinj.dev/linux-server-hardening-best-practices/#accessing-the-server">Accessing The Server</a></li><li><a href="https://razinj.dev/linux-server-hardening-best-practices/#more-on-ssh">More on SSH</a></li><li><a href="https://razinj.dev/linux-server-hardening-best-practices/#the-firewall">The Firewall</a></li><li><a href="https://razinj.dev/linux-server-hardening-best-practices/#conclusion">Conclusion</a></li></ul><h2 id="requirements">Requirements</h2><ul><li>Basic understanding of Linux and the command line</li></ul><h2 id="packages-updates">Packages Updates</h2><p>We all have a number of packages or apps installed and running in our systems and it&apos;s very important to update them regularly not just for the better performance and the features an update might introduce (don&apos;t be mistaken updates with upgrades, they might not be compatible with the ecosystem you have) but also for the security patches the developer&apos;s ship to us to fix vulnerabilities.</p><p>That&apos;s why regular updates are good but what if I told you they can be automated?</p><p>Great right, Automatic updates are not recommended for various reasons and what&apos;s recommended is to read the changelog for each update we would install to make sure it doesn&apos;t break our servers, however, security updates/patches are recommended to be installed, below we will see how to make them automatic.</p><p>We will be using the <a href="https://wiki.debian.org/UnattendedUpgrades?ref=razinj.dev">unattended-upgrades</a> package, we can install it with the following command:</p><pre><code class="language-SHELL">sudo apt install unattended-upgrades</code></pre><p>After installing it, run the following:</p><pre><code class="language-SHELL">sudo dpkg-reconfigure --priority=low unattended-upgrades</code></pre><p>This will bring a dialog, Choose <code>YES</code> and it will create the following config file:</p><pre><code>/etc/apt/apt.conf.d/20auto-upgrades</code></pre><p>Having this content</p><pre><code>APT::Periodic::Update-Package-Lists &quot;1&quot;;
APT::Periodic::Unattended-Upgrade &quot;1&quot;;</code></pre><p>We can further configure this tool to update and upgrade other stable packages or just security updates, just edit the following file:</p><pre><code class="language-SHELL">sudo nano /etc/apt/apt.conf.d/50unattended-upgrades</code></pre><p>You will find something similar to this</p><pre><code># head /etc/apt/apt.conf.d/50unattended-upgrades

// Note that in Ubuntu security updates may pull in new dependencies
// from non-security sources (e.g. chromium). By allowing the release
// pocket these get automatically pulled in.
Unattended-Upgrade::Allowed-Origins {
	&quot;${distro_id}:${distro_codename}&quot;;
	&quot;${distro_id}:${distro_codename}-security&quot;;
	// Extended Security Maintenance; doesn&apos;t necessarily exist for
	// every release and this system may not have it installed, but if
	// available, the policy for updates is such that unattended-upgrades
	// should also install from here by default.
	&quot;${distro_id}ESMApps:${distro_codename}-apps-security&quot;;
	&quot;${distro_id}ESM:${distro_codename}-infra-security&quot;;
//	&quot;${distro_id}:${distro_codename}-updates&quot;;
//	&quot;${distro_id}:${distro_codename}-proposed&quot;;
//	&quot;${distro_id}:${distro_codename}-backports&quot;;
};</code></pre><p>It&apos;s well documented so you won&apos;t find trouble configuring it.</p><p>And like this, we are installing security updates automatically providing some peace of mind.</p><h2 id="users">Users</h2><p>The first thing we hear in any blog or forum is to don&apos;t use the <code>root</code> user or its privileges as much as possible, so it&apos;s recommended to use a non-root user.</p><p>Create a user with the following command:</p><pre><code class="language-SHELL">sudo adduser MY_NAME</code></pre><p>Add the user to the <code>sudo</code> group &#xA0;to get its privileges on-demand:</p><pre><code class="language-SHELL">sudo usermod -aG sudo MY_NAME</code></pre><p>That&apos;s it, log out of this session and connect with this user.</p><h2 id="accessing-the-server">Accessing The Server</h2><p>There are various ways to access a Linux server, using VNC and/or SSH are common.</p><p>VNC by nature is not secure since its traffic is not encrypted but on the other hand, SSH is, and it&apos;s hard to break throw.</p><p>That&apos;s why I recommend using SSH as your only way to the server and SSH itself can be even more secure following the next steps:</p><p>Use keys to authenticate and not password, Let&apos;s create a private key (in your client/machine terminal not in the server):</p><pre><code class="language-SHELL">ssh-keygen -t rsa -b 4096 -C &quot;MY_NAME&apos;s server private key&quot;</code></pre><p>This command will generate 2 files, <code>id_rsa</code> and <code>id_rsa.pub</code> (the private and public key respectively).</p><p>Add your public key to the server (in most cases the path to the keys are in <code>~/.ssh</code> directory):</p><pre><code class="language-SHELL">ssh-copy-id -i /path/to/id_rsa.pub MY_NAME@IP_OF_SERVER</code></pre><p>Now you can use it to authenticate and access the server:</p><pre><code class="language-SHELL">ssh -i /path/to/id_rsa MY_NAME@IP_OF_SERVER</code></pre><p>You will be prompted to enter your key password, when done you will get into the server safely.</p><h2 id="more-on-ssh">More on SSH</h2><p>In case you don&apos;t or won&apos;t use IPv6 for SSH you can disable it to reduce the number of ways SSH can accept communications.</p><p>Open the following file:</p><pre><code class="language-SHELL">sudo nano /etc/ssh/sshd_config</code></pre><p>And change the <code>AddressFamily</code> property to <code>inet</code>:</p><pre><code>From &apos;AddressFamily any&apos; to &apos;AddressFamily inet&apos; # Which means IPv4 only</code></pre><p>Root access, you can disallow SSH root access:</p><pre><code>From &apos;PermitRootLogin yes&apos; to &apos;PermitRootLogin no&apos;</code></pre><p>And since we are using keys to authenticate, disallow password authentication:</p><pre><code>From &apos;PasswordAuthentication yes&apos; to &apos;PasswordAuthentication no&apos;</code></pre><p>Finally, Restart the SSH daemon for changes to take effect:</p><pre><code class="language-SHELL">sudo systemctl restart sshd</code></pre><h2 id="the-firewall">The Firewall</h2><p>Open ports are just a nightmare it&apos;s like having a house with no locked doors, you can imagine.</p><p>A firewall is basically a set of rules to control incoming and outgoing requests in your server, now when it comes to configuring the firewall in Linux using <code>iptables</code>; its a pain, that&apos;s why there is a package called <code>ufw</code> (which stands for &apos;Uncomplicated Firewall&apos;), Let&apos;s install it and configure it.</p><p>Install <code>ufw</code>:</p><pre><code class="language-SHELL">sudo apt install ufw</code></pre><p>Since we are using SSH it listens on port 22 so we will have to allow communications through that port:</p><pre><code class="language-SHELL"># Before doing anything, Reset any rule (Make sure it wasn&apos;t used by anyone before)
sudo ufw reset

# Enable UFW
sudo ufw enable

# Allow OpenSSH server (SSH)
sudo ufw allow 22</code></pre><p>Now before we wrap the firewall step, Again if you are not going to use IPv6, block it using <code>ufw</code> too, Let&apos;s do it (it&apos;s optional).</p><p>Open the following file:</p><pre><code class="language-SHELL">sudo nano /etc/default/ufw</code></pre><p>Change the <code>IPv6</code> property:</p><pre><code class="language-SHELL">From &apos;IPV6=yes&apos; to &apos;IPV6=no&apos;</code></pre><p>Now we can disable and enable <code>ufw</code> again for changes to take effect:</p><pre><code class="language-SHELL">sudo ufw disable &amp;&amp; sudo ufw enable</code></pre><p>Verify the changes:</p><pre><code class="language-SHELL">sudo ufw status verbose</code></pre><p>And remember if you are going to host a website or an API, you will need to open port <code>80</code> (HTTP) and/or <code>443</code> (HTTPS).</p><h2 id="conclusion">Conclusion</h2><p>After all the steps we have taken, we can call it a day hardening our Linux server.</p><hr><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html--><p></p>]]></content:encoded></item><item><title><![CDATA[Build and Run Apt-Cacher-NG Proxy in a Docker Container]]></title><description><![CDATA[Speed up Your Network With This Caching Server]]></description><link>https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/</link><guid isPermaLink="false">6237346e603fe9000130a29d</guid><category><![CDATA[Linux]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Docker Compose]]></category><dc:creator><![CDATA[RAZINJ]]></dc:creator><pubDate>Sun, 04 Jul 2021 09:00:00 GMT</pubDate><media:content url="https://razinj.dev/content/images/2022/03/build-and-run-apt-cacher-ng-proxy-in-a-docker-container.png" medium="image"/><content:encoded><![CDATA[<img src="https://razinj.dev/content/images/2022/03/build-and-run-apt-cacher-ng-proxy-in-a-docker-container.png" alt="Build and Run Apt-Cacher-NG Proxy in a Docker Container"><p><strong><a href="https://www.unix-ag.uni-kl.de/~bloch/acng?ref=razinj.dev">Apt-Cacher-NG</a></strong> is a write-through caching proxy that caches apt repositories metadata and packages for other hosts connected to it, the proxy is mostly used on the same network of the hosts to speed things up.</p><p>In this post, we will be building the Docker image and running the apt-cacher-ng server along with configuring the clients to use it as their apt caching proxy.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">&#x1F310;</div><div class="kg-callout-text">You can find the source code at the end of the post.</div></div><h2 id="table-of-contents">Table of contents</h2><ul><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#requirements">Requirements</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#do-we-need-a-proxy">Do We Need a Proxy?</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#why-use-it-inside-a-docker-container">Why Use It Inside a Docker Container?</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#setup-dockerfile">Setup Dockerfile</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#dockerfile-breakdown">Dockerfile Breakdown</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#what-is-passthroughpattern">What is PassThroughPattern?</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#setup-docker-compose-file">Setup Docker Compose file</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#start-and-verification">Start and Verification</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#clients-setup">Clients Setup</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#setup-verification">Setup Verification</a></li><li><a href="https://razinj.dev/build-and-run-apt-cacher-ng-proxy-in-docker/#conclusion">Conclusion</a></li></ul><h2 id="requirements">Requirements</h2><ul><li><a href="https://docs.docker.com/engine/install?ref=razinj.dev">Docker</a></li><li><a href="https://docs.docker.com/compose/install?ref=razinj.dev">Docker Compose</a></li><li>A text editor of your choice</li></ul><h2 id="do-we-need-a-proxy">Do We Need a Proxy?</h2><p>The main use case is having multiple machines running systems that use apt as their package manager, where you want to speed up the process of updating and upgrading metadata and packages.</p><h2 id="why-use-it-inside-a-docker-container">Why Use It Inside a Docker Container?</h2><p>Mainly for the idea of isolating the host from the apps running on it and the ease of management of those apps, Docker fits perfectly for this.</p><h2 id="setup-dockerfile">Setup Dockerfile</h2><p>In order to run the proxy in a Docker container, we need first to create a <code>Dockerfile</code> for it.</p><p>Let&apos;s start by creating a directory for the Docker file:</p><pre><code class="language-SHELL">mkdir custom-apt-cacher-ng</code></pre><p>Get into the directory and create the file:</p><pre><code class="language-SHELL">cd custom-apt-cacher-ng &amp;&amp; touch Dockerfile</code></pre><p>Paste the following content into it:</p><figure class="kg-card kg-code-card"><pre><code class="language-Dockerfile">FROM ubuntu:21.04

# Expose the cache directory for volume binding
VOLUME [&quot;/var/cache/apt-cacher-ng&quot;]

# Update apt-get cache
RUN apt-get update -y &amp;&amp; \
        # Install apt-cacher-ng package
        apt-get install apt-cacher-ng -y &amp;&amp; \
        # Clean up
        rm -rf /var/lib/apt/lists/*

# Expose the apt-cacher-ng port to be binded
EXPOSE 3142

# Set cache directory permissions
CMD chmod 777 /var/cache/apt-cacher-ng &amp;&amp; \
        # Append PassThroughPattern config for SSL/TLS proxying (optional)
        echo &quot;PassThroughPattern: .*&quot; &gt;&gt; /etc/apt-cacher-ng/acng.conf &amp;&amp; \
        # Start the service
        /etc/init.d/apt-cacher-ng start &amp;&amp; \
        # Output all logs of apt-cacher-ng
        tail -f /var/log/apt-cacher-ng/*
</code></pre><figcaption>Dockerfile</figcaption></figure><h2 id="dockerfile-breakdown">Dockerfile Breakdown</h2><p>Let&apos;s explain this Dockerfile, we see that we are based on a stable version of Ubuntu with version <code>21.04</code> and, exposing the caching directory to be used for persistence, and running the main command which is installing the <code>apt-cacher-ng</code> package itself, then exposing the port the package uses, and finally, we are setting up permissions and appending the <code>PassThroughPattern</code> (we will get to this below) before starting the service.</p><h2 id="what-is-passthroughpattern">What is PassThroughPattern?</h2><p>This config allows us to proxy/tunnel everything including the secured repositories via SSL/TLS throw the <code>apt-cacher-ng</code> server, keep in mind that it will not cache the secured ones since the traffic is encrypted.</p><h2 id="setup-docker-compose-file">Setup Docker Compose file</h2><p>Let&apos;s set up the Docker Compose file, now this is an optional part but it&apos;s very recommended.</p><p>Create the file in the same root directory:</p><pre><code class="language-SHELL">touch docker-compose.yml</code></pre><p>Paste the following content into it:</p><figure class="kg-card kg-code-card"><pre><code class="language-YAML">version: &quot;3&quot;

services:
  app:
    build: .
    image: custom-apt-cacher-ng
    container_name: apt-cacher-ng
    restart: unless-stopped
    ports:
      # To connect to apt-cacher-ng from outside of local host, change 127.0.0.1 to 0.0.0.0 or whatever interface you want
      - 127.0.0.1:3142:3142
    volumes:
      - ./cache:/var/cache/apt-cacher-ng</code></pre><figcaption>docker-compose.yml</figcaption></figure><p>This Docker Compose file will expose the <code>3142</code> port (feel free to change it to whatever you want) and bind the local directory <code>cache</code> in the host for the <code>apt-cacher-ng</code> caching directory.</p><h2 id="start-and-verification">Start and Verification</h2><p>We can start the proxy in two ways, Docker or Docker Compose, we will cover both of them below.</p><h3 id="the-docker-way">The Docker way</h3><p>Build the proxy image:</p><pre><code class="language-SHELL">docker build -t custom-apt-cacher-ng .</code></pre><p>Run an instance of that image in detached mode:</p><pre><code class="language-SHELL">docker run -d -p 127.0.0.1:3142:3142 --name apt-cacher-ng custom-apt-cacher-ng</code></pre><p>Now the container should be running, to verify run:</p><pre><code class="language-SHELL">docker ps -a | grep &quot;custom-apt-cacher-ng&quot;</code></pre><h3 id="the-docker-compose-way-recommended">The Docker Compose way (recommended)</h3><p>Build the proxy image:</p><pre><code class="language-SHELL">docker-compose build</code></pre><p>Run the service using:</p><pre><code class="language-SHELL">docker-compose up -d</code></pre><p>You can verify that the container is up using the same command as above.</p><p>We are done setting up the proxy, the next step will be setting up the clients to use the cache proxy.</p><h2 id="clients-setup">Clients Setup</h2><p>We can use the host where the server lives as a client for the proxy too, and that&apos;s what we will be doing here as a first step.</p><p>Create an <code>apt</code> proxy file:</p><pre><code class="language-SHELL">sudo touch /etc/apt/apt.conf.d/01proxy</code></pre><p>Paste the following in the proxy file</p><figure class="kg-card kg-code-card"><pre><code>Acquire::http { Proxy &quot;http://SERVER_IP:3142&quot;; };</code></pre><figcaption>01proxy</figcaption></figure><p>Make sure to change the <code>SERVER_IP</code> before saving (It will be <code>localhost</code> or <code>127.0.0.1</code> in this client setup).</p><p>Now before we start installing packages with the proxy we need to reset the <code>apt</code> lists and cache.</p><p>Execute the following commands each at a time:</p><pre><code class="language-SHELL">sudo apt-get clean
cd /var/lib/apt
sudo mv lists lists.bak # or delete it, preferred to keep a backup
sudo mkdir -p lists/partial
sudo apt-get clean
sudo apt-get update</code></pre><p>That&apos;s it, we are done setting up the server and the client for the proxying process.</p><h2 id="setup-verification">Setup Verification</h2><p>Let&apos;s verify that the setup is working via a simple <code>sudo apt-get update</code>, then head to <code>http://localhost:3142/acng-report.html</code> and we should see some content being downloaded and cached for the next requests.</p><h2 id="conclusion">Conclusion</h2><p>We just set up the <code>apt</code> proxy using either Docker or Docker Compose with ease, and we tested the setup using the local host.</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://github.com/razinj/docker-custom-apt-cacher-ng?ref=razinj.dev"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - razinj/docker-custom-apt-cacher-ng: Docker image for apt-cacher-ng that allows SSL/TLS proxying along with exposing the cache volume</div><div class="kg-bookmark-description">Docker image for apt-cacher-ng that allows SSL/TLS proxying along with exposing the cache volume - GitHub - razinj/docker-custom-apt-cacher-ng: Docker image for apt-cacher-ng that allows SSL/TLS pr...</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt="Build and Run Apt-Cacher-NG Proxy in a Docker Container"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">razinj</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/4d13bc4b1c558e7369a6267ad0f098c22191bb398d7bd21d3ff36032fc9df720/razinj/docker-custom-apt-cacher-ng" alt="Build and Run Apt-Cacher-NG Proxy in a Docker Container"></div></a><figcaption>Source Code</figcaption></figure><hr><!--kg-card-begin: html--><p>As always, I hope you learned something.</p>
<p>Found this useful? feel free to share it with your friends.</p>
<p>
Join the newsletter from <a class="umami--click--post-footer-newsletter-link" href="https://razinj.dev/#/portal/signup" target="_self">here</a> to notify you of new posts and updates.
</p>
<p>
Like the post? consider buying us a coffee &#x2764;&#xFE0F;.
</p>
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="razinj.dev" data-color="#FFDD00" data-emoji data-font="Lato" data-text="Buy us a coffee" data-outline-color="#000000" data-font-color="#000000" data-coffee-color="#ffffff"></script><!--kg-card-end: html--><p></p>]]></content:encoded></item></channel></rss>