<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Manu</title>
    <description>The latest articles on DEV Community by Manu (@ebreton).</description>
    <link>https://dev.to/ebreton</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F6182%2F32407cb5-2dcb-4fbf-807a-87f5642edc8b.png</url>
      <title>DEV Community: Manu</title>
      <link>https://dev.to/ebreton</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ebreton"/>
    <language>en</language>
    <item>
      <title>make release: save time, gain in reliability</title>
      <dc:creator>Manu</dc:creator>
      <pubDate>Wed, 27 Jun 2018 09:12:15 +0000</pubDate>
      <link>https://dev.to/ebreton/make-release-2iko</link>
      <guid>https://dev.to/ebreton/make-release-2iko</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--krC99TWb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2sz1pxsnuzu89ypwspn8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--krC99TWb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2sz1pxsnuzu89ypwspn8.png" alt="make release: save time, gain in reliability"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The best way to make a diamond-solid process is to automate it&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  A story, where any resemblance to reality is pure coincidence...
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;[fictitious user] : ... and there I have a page which says five hundred errors. Thats's a lot !  &lt;/p&gt;

&lt;p&gt;[you, not mentioning that 500 is an error code, not the number of errors] :  Hmmm... should not be like this.... which version are you running ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ah.&lt;/p&gt;

&lt;p&gt;Which version exactly ?&lt;/p&gt;

&lt;p&gt;Tricky question... small numbers in the footer of the footer ? hidden comments ? versionned-urls for APIs ? and so on...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[user] :It says 0.3.1  &lt;/p&gt;

&lt;p&gt;[you] : Thanks. Let me check the Changelog&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ah.&lt;/p&gt;

&lt;p&gt;Ah.&lt;/p&gt;

&lt;p&gt;Where is the Changelog exactly ?&lt;/p&gt;

&lt;p&gt;Do we have one to start with ? Is it up to date ? Has it been written for marketing guys ? Well, chances are huge that if you have a Changelog, it does not really help anyway...&lt;/p&gt;

&lt;h1&gt;
  
  
  Objective
&lt;/h1&gt;

&lt;p&gt;In order to facilitate the conversations of the kind above, I would like to present you with a bit of processing and a bit of tooling.&lt;/p&gt;

&lt;p&gt;The final objective should be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make release
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you would be set with&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a new branch on github rc-X.X.X&lt;/li&gt;
&lt;li&gt;a new release on github based on this branch&lt;/li&gt;
&lt;li&gt;an updated Changelog with meaningful information ...&lt;/li&gt;
&lt;li&gt;... that actually comes from the differences between this release and the previous one&lt;/li&gt;
&lt;li&gt;a way to check the current / next version of your code&lt;/li&gt;
&lt;li&gt;an updated badge on your README (because badges are cool)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dMUXB5PO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/05/objective-numbered.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dMUXB5PO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/05/objective-numbered.png" alt=""&gt;&lt;/a&gt;One command to rule them all&lt;/p&gt;

&lt;h1&gt;
  
  
  Pre-requisites
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;make&lt;/li&gt;
&lt;li&gt;your application on github&lt;/li&gt;
&lt;li&gt;docker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker will relieve you from installing other dependencies like ruby (used to generate Changelogs) or python (used to manage version)&lt;/p&gt;

&lt;h1&gt;
  
  
  Set up
&lt;/h1&gt;

&lt;h2&gt;
  
  
  files
&lt;/h2&gt;

&lt;p&gt;This repository (&lt;a href="https://github.com/ebreton/release-maker"&gt;&lt;strong&gt;release-maker&lt;/strong&gt;&lt;/a&gt;) is designed as a plugin: you should copy paste the following files into the repository of your app&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;├── Makefile&lt;/td&gt;
&lt;td&gt;entrypoint for all commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;├── bin&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;│   ├── Dockerfile&lt;/td&gt;
&lt;td&gt;defines a (very) simple docker image FROM python:3 with the &lt;em&gt;requests&lt;/em&gt; library available. The image is built locally with &lt;code&gt;make build&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;│   └── update_release.py&lt;/td&gt;
&lt;td&gt;management script, to run sanity checks, update version numbers, publish release to github&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;├── .env.sample&lt;/td&gt;
&lt;td&gt;to define the values of your environment variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;└── versions.py&lt;/td&gt;
&lt;td&gt;where version numbers are written to/read/imported from&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  environment variables
&lt;/h2&gt;

&lt;p&gt;simply copy-paste (or rename) the &lt;em&gt;.env.sample&lt;/em&gt; file to &lt;em&gt;.env&lt;/em&gt; and fill in your details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cp .env.sample .env
$ vi .env
# GITHUB_* are used for github APIs, to automatically create release &amp;amp; Changelog
GITHUB_OWNER?=repo owner
GITHUB_REPO?=repo name
GITHUB_USER?=your github user
GITHUB_KEY?=your key
CHANGELOG_GITHUB_TOKEN?=${GITHUB_KEY}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  docker
&lt;/h2&gt;

&lt;p&gt;Once you have "imported" the repository content and defined your vars, you need to build / pull the docker images that will be used. Otherwise you will get this kind of error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make vars
Unable to find image 'python-requests:latest' locally
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The python image is built locally with &lt;code&gt;make build&lt;/code&gt;. It is a straightforward &lt;em&gt;Dockerfile&lt;/em&gt; that relies on python 3 official image and will have the library &lt;em&gt;requests&lt;/em&gt; installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# bin/Dockerfile
FROM python:3

WORKDIR /usr/src/app
RUN pip install --no-cache-dir requests

ENTRYPOINT ["python"]
CMD ["--help"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we are setting up docker, you can actually pull the second image that will generate the Changelog. &lt;code&gt;make pull&lt;/code&gt; will also run &lt;code&gt;make build&lt;/code&gt; for you&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make pull
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command should now work properly (with your values for the environment variables)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make vars
  Version: 0.1.1-rc

  GITHUB_OWNER=ebreton
  GITHUB_REPO=release-maker
  GITHUB_USER=ebreton
  GITHUB_TOKEN=xxx
  CHANGELOG_GITHUB_TOKEN=xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  versions
&lt;/h2&gt;

&lt;p&gt;The generation of the Changelog depends on the existence of (at least) one git tag. If you have none, you should run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git tag 0.1.0 # or whatever initial version number you prefer
git push --tags
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check the current configuration with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make version
CHANGELOG GENERATOR:
Version: 1.14.3

APPLICATION:
Version: 0.1.1-rc

Updating release numbers...
INFO - compute - will set _version=0.1.1-rc
INFO - compute - will set _build=5026db3b8b001c5d8991c90d0528161abec7bbeb
INFO - compute - will set _release=0.1.0-12-g5026db3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Usage
&lt;/h1&gt;

&lt;p&gt;Practically, you will only use &lt;code&gt;make release&lt;/code&gt;, everytime you want to make a release (surprised?)&lt;/p&gt;

&lt;p&gt;and that's all.&lt;/p&gt;

&lt;p&gt;Of course, there is a few more commands, some we have seen to setup things, some to diagnose or check what the situation is. Here is the list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;remarks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;display the current version of the application (not the one previously deployed), and version of github-changelog-generator&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make vars&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;display value of environment variables&lt;/td&gt;
&lt;td&gt;requires &lt;em&gt;.env&lt;/em&gt; file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;build a docker image FROM python:3 with requests available&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make pull&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;pull last docker image of github-changelog-generator&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make ps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list docker containers (with less information than docker ps)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make changelog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;build/update new version of Changelog according to tags and PRs&lt;/td&gt;
&lt;td&gt;valid environment variables must be defined in &lt;em&gt;.env&lt;/em&gt; (or environment)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make release&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;after confirmation of version number, execute all release process.&lt;/td&gt;
&lt;td&gt;must be executed in branch &lt;strong&gt;master&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make push-qa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;update tag &lt;strong&gt;qa-release&lt;/strong&gt; and rebuild Changelog&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make push-prod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;update tag q &lt;strong&gt;a-release&lt;/strong&gt; and rebuild Changelog&lt;/td&gt;
&lt;td&gt;ask confirmation of branch and version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We have not yet discussed the last two lines... there is some kind of bonus behind them, that will see in the last section.&lt;/p&gt;

&lt;h1&gt;
  
  
  Behind the curtains
&lt;/h1&gt;

&lt;p&gt;I guess you see it coming :&lt;/p&gt;

&lt;h2&gt;
  
  
  1. a new branch on github rc-X.X.X
&lt;/h2&gt;

&lt;p&gt;this acts as an emergency branch, in case when a quick fix is needed&lt;/p&gt;

&lt;h2&gt;
  
  
  2. a new release on github based on this branch
&lt;/h2&gt;

&lt;p&gt;this allows to update the badge or get the release number from github API&lt;/p&gt;

&lt;h2&gt;
  
  
  3. an updated Changelog with meaningful information ...
&lt;/h2&gt;

&lt;p&gt;this is the mandatory context that you will be looking for to diagnose any issue&lt;/p&gt;

&lt;h2&gt;
  
  
  4. that actually comes from the differences between this release and the previous one
&lt;/h2&gt;

&lt;p&gt;Thanks &lt;a href="https://github.com/ferrarimarco/docker-github-changelog-generator"&gt;ferrarimarco&lt;/a&gt; !&lt;/p&gt;

&lt;h2&gt;
  
  
  5. a way to check the current / next version of your code
&lt;/h2&gt;

&lt;p&gt;thanks to &lt;em&gt;versions.py&lt;/em&gt; which you can import and use anywhere in your code&lt;/p&gt;

&lt;h2&gt;
  
  
  6. an updated badge on your README (because badges are cool)
&lt;/h2&gt;

&lt;p&gt;indeed&lt;/p&gt;

&lt;h1&gt;
  
  
  Bonus
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Using git tags for continuous integration and continuous deployment&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you happen&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;to run your applications within docker containers,&lt;/li&gt;
&lt;li&gt;and have setup automated builds in your registry (or docker hub),&lt;/li&gt;
&lt;li&gt;to use an orchestrator&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is convenient to use tags for continuous integration and deployment&lt;/p&gt;

&lt;p&gt;In my case, the orchestrator relies on the &lt;code&gt;qa-release&lt;/code&gt; tag to upgrade the integration environment, and on the &lt;code&gt;prod-release&lt;/code&gt; tag to upgrade the production.&lt;/p&gt;

&lt;p&gt;It is all automated, except for the trigger, which remains on your hand. Therefore, running &lt;code&gt;make release&lt;/code&gt; will call &lt;code&gt;make push-qa&lt;/code&gt;, and start the upgrade process for integration. You can also upgrade without going through all the release process, by calling yourself &lt;code&gt;make push-qa&lt;/code&gt; (no confirmation)&lt;/p&gt;

&lt;p&gt;In order to update production, you must call &lt;code&gt;make push-prod&lt;/code&gt;, which will ask for your confirmation.&lt;/p&gt;

&lt;p&gt;Be aware that those two commands will update your Changelog (by calling &lt;code&gt;make changelog&lt;/code&gt;), because we want it up to date with the commits that are pointed out by each tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make push-qa
# update tags
git tag -f qa-release
Updated tag 'qa-release' (was db42b2e)
git push --tags --force
...

$ make push-prod
You are on branch 'master'
on version '0.1.2-rc'

Type [y] to comfirm push
or any other key to abort:
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Needless to say, if you haven't configured your orchestrator to update containers based on new builds, and if you haven't configured your registry to update builds based on git tags.... well... those two commands above will not be so useful.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I cannot stress enough the conveniency of automatic versioning and releasing, along with a consistent (auto-magically generated) Changelog. It just saved hours of team-work to support the final users and diagnose the issues.&lt;/p&gt;

&lt;p&gt;Within my team, we were aware of it for a long time, and we had tried hard to structure our Pull Requests, our Changelog, our version numbers. But that just could not work as long as it was manual. Too cumbersome, with absolutely no value (until the shit hit the fan).&lt;/p&gt;

&lt;p&gt;Firstofall &lt;em&gt;release maker&lt;/em&gt; has allowed us to focus on development, on the content of the reviews. Secondly, it  has facilitated the release process so much that we could actually increase dramatically our release frequency, from once a month to once a week (sometimes more).&lt;/p&gt;

&lt;p&gt;Nice. Handy.&lt;/p&gt;

&lt;p&gt;Hope you will find it useful too :)&lt;/p&gt;

</description>
      <category>docker</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>ci</category>
    </item>
    <item>
      <title>Ghost in A shell - part IV (and final): Maintenance</title>
      <dc:creator>Manu</dc:creator>
      <pubDate>Wed, 13 Jun 2018 15:28:52 +0000</pubDate>
      <link>https://dev.to/ebreton/ghost-in-a-shell---part-iv-and-final-maintenance-e6o</link>
      <guid>https://dev.to/ebreton/ghost-in-a-shell---part-iv-and-final-maintenance-e6o</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8a5pRpXO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/GIAS-maintenance.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8a5pRpXO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/GIAS-maintenance.png" alt="Ghost in A shell - part IV (and final): Maintenance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One very good reason to go for &lt;a href="https://ghost.org/features/"&gt;Ghost(Pro)&lt;/a&gt; is that you do not have to maintain your blogs. They just run. For ever. Eventhough servers do crash, hard drives do break, electric power do fail, blogs do need to be upgraded... they will continue serving their pages.&lt;/p&gt;

&lt;p&gt;Nevertheless, having one's own setup is fun (as long as you are aware of the limits above), and much easier to play with. You could for instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run the Ghost version you want:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ GHOST_VERSION=1.10 PORT=3002 NAME=old make

$ NAME=old make cli-version
  docker exec -it old ghost -v
  Ghost-CLI version: 1.1.1
  Ghost Version (at /var/lib/ghost): 1.10.0
  Latest version on Docker Hub: 1.24.2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;maintain many blogs without having to remember the options they were built with:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# instead of 
$ PORT=3003 NAME=new URI=my-new-super-blog DOMAIN=me.com make restart

# just use
$ p=instances/new/ make restart

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;upgrade your blog with no risk and no hassle
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ vi instances/old/.env
  GHOST_VERSION=1.24

$ make p=instances/old/ upgrade
  docker exec -it old ghost -v
  Ghost-CLI version: 1.8.1
  Ghost Version (at /var/lib/ghost): 1.24.2
  Latest version on Docker Hub: 1.24.2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And these three reasons are good enough for me to write this fourth (and last) blog about shells of Ghost . Did I say "fourth" ? yep!. And here are the three first ones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ebreton/ghost-in-a-shell---part-i--localhost-5he9"&gt;Part I : localhost&lt;/a&gt;, that focuses on localhost&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ebreton/ghost-in-a-shell---part-ii---https-is-the-norm-1jj4"&gt;Part II : HTTPs is the norm&lt;/a&gt;, that shows you how to spawn multiple ghost instances on a given server&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ebreton/ghost-in-a-shell---part-iii--to-production-with-performance-10g5"&gt;Part III: To production, with performance&lt;/a&gt;, that applies the recommendations from the Ghost team when it comes to DB + emailing, and puts a bit of pressure on the resulting instances&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But first of all you need the basic diagnostic tools&lt;/p&gt;

&lt;h1&gt;
  
  
  Environment variables
&lt;/h1&gt;

&lt;p&gt;We have seen before the two following commands&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;make ps&lt;/code&gt; to list the running instances&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make logs&lt;/code&gt; to tail and follow the logs, i.e to see what is happening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those commands depends on the environment vars, which can be overridden in the command line. E.g.:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;make vars&lt;/code&gt; will list for you the currently configured variables
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make vars
# common
 NAME=ghost-local
 GHOST_VERSION=1
# values used for dev
 PORT=3001
# values used by traefik (qa &amp;amp; prod)
 PROTOCOL=http
 DOMAIN=localhost
 URI=ghost-local

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;NAME=my-blog URI=me PROTOCOL=https GHOST_VERSION=1.22 make vars&lt;/code&gt; will show how the variable are overriden
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make vars
# common
 NAME=my-blog
 GHOST_VERSION=1.22
# values used for dev
 PORT=3001
# values used by traefik (qa &amp;amp; prod)
 PROTOCOL=https
 DOMAIN=localhost
 URI=me

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far (in the previous blogs) this is the way to go with multiple instances... and it sucks, especially if you want to run different set of values for each of them (which is exactly the reason why we have this series in the first place)&lt;/p&gt;

&lt;h1&gt;
  
  
  All-in-one configurations
&lt;/h1&gt;

&lt;p&gt;Therefore, there is a newcomer in the list of available variables: please welcome &lt;code&gt;p&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;p&lt;/code&gt; is the path of the valid instance of the blog you want to act upon. you can use it with most of the &lt;code&gt;make&lt;/code&gt; commands we have seen so far.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Valid&lt;/em&gt; means that the path will contain a &lt;em&gt;.env&lt;/em&gt; file with all the overriden variables. For the sake of your sanity, this file is automatically generated when you spawn your instances with &lt;code&gt;make [dev|qa|prod]&lt;/code&gt;. e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ NAME=new-blog PORT=3002 make
# Simply start a ghost container making it directly available through $PORT
docker run --rm -d --name new-blog \
        -v /Users/emb/git-repos/ghost-in-a-shell/instances/new-blog:/var/lib/ghost/content \
        -p 3002:2368 \
        -e url=http://localhost:3002 \
        ghost:1-alpine
6330f60a50a834c7acee5916751815e47bba2f68b98f150a6ba2fe59627704ff
make save-vars
creating instances/new-blog/.env

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two lines are the new sweetie... and you can now run the following to make sure your configuration as been saved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make p=instances/new-blog/ vars
# common
  p=instances/new-blog/.env
  NAME=new-blog
  GHOST_VERSION=1
# values used for dev
  PORT=3002
# values used by traefik (qa &amp;amp; prod)
  PROTOCOL=http
  DOMAIN=localhost
  URI=new-blog

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consequently&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;make p=instances/new-blog/ vars&lt;/code&gt; will list all vars from &lt;em&gt;./instances/new-blog/.env&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make p=instances/new-blog/ logs&lt;/code&gt; will tail and follow the logs of the &lt;em&gt;./instances/new-blog&lt;/em&gt; blog instance&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make p=instances/new-blog/ restart&lt;/code&gt; will restart the blog with the settings defined in &lt;em&gt;./instances/new-blog/.env&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make p=instances/new-blog/ cli-version&lt;/code&gt; show the versions for the new blog&lt;/li&gt;
&lt;li&gt;and so on...&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that 1) the path is relative, 2) the trailing &lt;code&gt;/&lt;/code&gt; is mandatory, but it should come with the bash autocompletion anyway. (that's why it is done like this)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Upgrade (and Downgrade)
&lt;/h1&gt;

&lt;p&gt;Upgrading may be painful (particularly if you do not do it often enough). It is for sure scary. No ones want to change (break) something that runs smoothly.&lt;/p&gt;

&lt;p&gt;You first need a bit of information, e.g with the question 'how far am I behind ?'. &lt;code&gt;make cli-version&lt;/code&gt; will help you there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make cli-version
docker exec -it ghost-local ghost -v
Ghost-CLI version: 1.8.0
Ghost Version (at /var/lib/ghost): 1.23.0
Latest version on Docker Hub: 1.24.2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The upgrade mechanism relies on &lt;a href="https://hub.docker.com/r/library/ghost/tags/"&gt;DockerHub tags&lt;/a&gt;. Which means that the most generic tags point to the latest version, and that the same version may have multiple tags. For instance, if the latest version is 1.24.2, you will get this version with all the following tags: 1.24.2, 1.24, 1, latest.&lt;/p&gt;

&lt;p&gt;Hence the following behavior when upgrading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GHOST_VERSION=1&lt;/code&gt; -&amp;gt; will automatically update to all new releases of 1st version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GHOST_VERSION=1.x&lt;/code&gt; -&amp;gt; will automatically update for patches only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GHOST_VERSION=1.x.x&lt;/code&gt; -&amp;gt; will never automatically update&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default, the GHOST_VERSION is set to &lt;code&gt;1&lt;/code&gt;. Therefore, upgrading is as simple as &lt;code&gt;make upgrade[-qa|-prod]&lt;/code&gt;. This command will pull the lastest (1.x.x) docker image and restart the container for you.&lt;/p&gt;

&lt;p&gt;You can force your desired GHOST_VERSION by overriding the env var:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ GHOST_VERSION=1.24 make upgrade
...
Ghost-CLI version: 1.8.0
Ghost Version (at /var/lib/ghost): 1.24.2
Latest version on Docker Hub: 1.24.2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have specified a GHOST_VERSION when you created the blog instance, e.g: &lt;code&gt;GHOST_VERSION=1.10 PORT=3002 NAME=old make&lt;/code&gt;, the version is stored in the &lt;em&gt;.env&lt;/em&gt; file. Therefore the &lt;code&gt;p&lt;/code&gt; helper variable will not allow you to upgrade automatically. It will stick to the version of the instance specified when it was created, e.g:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ p=instances/old/ make upgrade
...
docker exec -it old ghost -v
Ghost-CLI version: 1.1.1
Ghost Version (at /var/lib/ghost): 1.10.0
Latest version on Docker Hub: 1.24.2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will have to edit manually your &lt;em&gt;./instance/$NAME/.env&lt;/em&gt; file and set the new GHOST_VERSION to upgrade it.&lt;/p&gt;

&lt;p&gt;Would you like to rollback to a previous version, e.g. version 1.23.0? Just modify againt the &lt;em&gt;.env&lt;/em&gt; file and upgrade again. Or pass the variables through the command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ vi instances/old/.env
GHOST_VERSION=1.23.0

$ make p=instances/old/ upgrade
docker exec -it old ghost -v
Ghost-CLI version: 1.8.1
Ghost Version (at /var/lib/ghost): 1.23.0
Latest version on Docker Hub: 1.24.2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;the actual docker image downloaded is &lt;code&gt;ghost:1.x.x-alpine&lt;/code&gt;: the &lt;em&gt;-alpine&lt;/em&gt; suffix is added here because I prefer it lighter than heavier.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Reliability ?
&lt;/h1&gt;

&lt;p&gt;Well, we have seen quite a lot with these series and reliability has more to do with the hosting you are going to choose. And security policies you are going to enforce too. Far too much and too wide for a simple tutorial like this one. Remember also the disclaimer from the begnining: go for Ghost pro if you are looking for reliability :)&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br&gt;
Manu&lt;/p&gt;

</description>
      <category>docker</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>ghost</category>
    </item>
    <item>
      <title>Ghost in A shell - part III : to Production, with performance</title>
      <dc:creator>Manu</dc:creator>
      <pubDate>Mon, 04 Jun 2018 20:35:29 +0000</pubDate>
      <link>https://dev.to/ebreton/ghost-in-a-shell---part-iii--to-production-with-performance-10g5</link>
      <guid>https://dev.to/ebreton/ghost-in-a-shell---part-iii--to-production-with-performance-10g5</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BrPhpqqH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/Ghost-in-A-shell.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BrPhpqqH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/Ghost-in-A-shell.png" alt="Ghost in A shell - part III : to Production, with performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am actually very happy with the outcome of the &lt;a href="https://dev.to/ebreton/ghost-in-a-shell---part-ii---https-is-the-norm-1jj4"&gt;previous blog&lt;/a&gt; for &lt;em&gt;my&lt;/em&gt; production: it has a proxy, it serves on HTTPs, sqlite3 is not so bad after all... and for my 7.5 users, it is just &lt;em&gt;great&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Your definition of "great" might be slightly different...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What about defining &lt;em&gt;production greatness&lt;/em&gt; as a setup that will give satisfaction based on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;performance (covered in this blog)&lt;/li&gt;
&lt;li&gt;ease of maintenance (covered in next blog)&lt;/li&gt;
&lt;li&gt;reliability (not sure that this will fit into the scope of the Tutorial)&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Soooo... let's start with performance.&lt;/p&gt;

&lt;h1&gt;
  
  
  Philosophy
&lt;/h1&gt;

&lt;p&gt;I am &lt;strong&gt;not&lt;/strong&gt; going to dive into the depth of the performance theme, too many things to cover. Let me just suggest this path: to focus on setting up a toolbox that will allow you&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;to run performance/load tests easily (one command sounds good, as usual; it will be &lt;code&gt;make gatling&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;to view the output metrics easily (like in a web browser, thank you gatling!)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The idea behind is to base our decisions upon relative metrics, which means we are going to compare the results of the tests before and after making some changes (idealy: any change). Changes will be validated according to the variation of the results&lt;/p&gt;

&lt;p&gt;But enough talking, let's make sure we are ready to go...&lt;/p&gt;

&lt;h1&gt;
  
  
  Pre-requisites
&lt;/h1&gt;

&lt;h2&gt;
  
  
  a) from the previous episodes...
&lt;/h2&gt;

&lt;p&gt;I will make the assumption that you have been through my two first blogs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ebreton/ghost-in-a-shell---part-i--localhost-5he9"&gt;Part I : localhost&lt;/a&gt;, that gets you familiar with the main commands of the &lt;em&gt;Makefile&lt;/em&gt;, and focus on localhost&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/ebreton/ghost-in-a-shell---part-ii---https-is-the-norm-1jj4"&gt;Part II : HTTPs is the norm&lt;/a&gt;, that shows you how to spawn ghost instances fearlessly on one server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are no new dependencies, which means you are good to go with &lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt; and the two repos &lt;a href="https://github.com/ebreton/prod-stack"&gt;prod-stack&lt;/a&gt; and &lt;a href="https://github.com/ebreton/ghost-in-a-shell"&gt;ghost-in-a-shell&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  b) if you wish to send emails (probability is high)
&lt;/h2&gt;

&lt;p&gt;The Ghost Team makes some recommendations to send email, one solution being to &lt;a href="https://docs.ghost.org/docs/mail-config#section-mailgun"&gt;leverage MailGun&lt;/a&gt;. Follow the link, and the steps behind this link.&lt;/p&gt;

&lt;p&gt;That will give you credentials to send emails through their platform. Those credentials will go into &lt;em&gt;./etc/prod.env&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cp etc/prod.env.sample etc/prod.env
$ vi etc/prod.env
  ...
  # see https://docs.ghost.org/docs/mail-config#section-mailgun
  # you will find an updated screencast to understand exactly where to find these details
  MAILGUN_LOGIN=(Sandbox) default SMTP Login
  MAILGUN_PASSWORD=(Sandbox) default Password

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those variables will be used when spawning an instance in "production mode", as we can see within the Makefile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prod:
    ...
    -e mail__transport=SMTP \   
    -e mail __options__ service=Mailgun \
    -e mail __options__ auth__user=${MAILGUN_LOGIN} \
    -e mail __options__ auth__pass=${MAILGUN_PASSWORD} \
    ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because we are now dealing with a 'production' environment, the setup is not as easy as it was when using your SMTP server... hence a few "surprises" on the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MailGun will ask you for a credit card, even if you keep using their free plan&lt;/li&gt;
&lt;li&gt;With the default login and password, you will have to declare all the recipients you wish to send emails to (and they will have to approve receiving those emails)&lt;/li&gt;
&lt;li&gt;To go around this limitation, you will have to set up your domain within MailGun. (and you will need to modify your DNS zone for this)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, if you prefer not using MailGun, you can remove the 4 lines above from the &lt;code&gt;make prod&lt;/code&gt; command, or adapt it to another service provider (let me know how it went if you do, please!)&lt;/p&gt;

&lt;p&gt;Next step is setting up the DB...&lt;/p&gt;

&lt;h1&gt;
  
  
  Switching the DB
&lt;/h1&gt;

&lt;h2&gt;
  
  
  a) objective
&lt;/h2&gt;

&lt;p&gt;We wish to make use of MySQL instead of sqlite3, because it is &lt;a href="https://docs.ghost.org/docs/install#section-install-mysql"&gt;recommended&lt;/a&gt; by the Ghost team. Fair enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  b) setup
&lt;/h2&gt;

&lt;p&gt;The prod-stack already runs a MariaDB in a container, as defined in the &lt;a href="https://github.com/ebreton/prod-stack/blob/master/docker-compose.yml#L29"&gt;docker-compose.yml&lt;/a&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  db-shared:
    image: mariadb:latest
    container_name: db-shared
    restart: always
    env_file:
      - $PWD/etc/db.env
    volumes:
      - db_data:/var/lib/mysql

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is only one piece of customization, the &lt;em&gt;db.env&lt;/em&gt; file which holds the MYSQL_ROOT_PASSWORD. This file has been generated for you when you installed the prod-stack by running &lt;code&gt;make&lt;/code&gt;. Here is the reminder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# from prod-stack root directory
$ make
cp etc/traefik.toml.sample etc/traefik.toml
cp etc/db.sample.env etc/db.env
sed -i s/password/auMr9jsMrPnX0Oatb9j4yX2AYBN2jTQV/g etc/db.env
Generated etc/db.env
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, I guess you know what is coming: copy the value from &lt;em&gt;prod-stack/etc/db.env&lt;/em&gt; and paste it in your &lt;em&gt;ghost-in-a-shell/etc/prod.env&lt;/em&gt; file. It now should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# password set for db-shared, as defined in prod-stack/etc/db.env
MYSQL_ROOT_PASSWORD=auMr9jsMrPnX0Oatb9j4yX2AYBN2jTQV

# see https://docs.ghost.org/docs/mail-config#section-mailgun
# you will find an updated screencast to understand exactly where to find these details
MAILGUN_LOGIN=postmaster@your.domain.com
MAILGUN_PASSWORD=your-key

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done? You are ready to make use of a MariaDB instead of sqlite3 thanks to the following line in the Makefile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prod:
    ...
    -e database__client=mysql \
    -e database __connection__ host=db-shared \
    -e database __connection__ user=root \
    -e database __connection__ password=${MYSQL_ROOT_PASSWORD} \
    -e database __connection__ database=${NAME} \
    ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a note, you can see that the connection to the DB server is hard-coded: it is made through the container name &lt;code&gt;db-shared&lt;/code&gt; which is defined in the &lt;em&gt;prod-stack/&lt;a href="https://github.com/ebreton/prod-stack/blob/master/docker-compose.yml#L29"&gt;docker-compose.yml&lt;/a&gt;&lt;/em&gt; as seen previously.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;code&gt;make prod&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;In the two sections above, we have created a &lt;em&gt;etc/prod.env&lt;/em&gt; file with three environment variables. This allow us to call &lt;code&gt;make prod&lt;/code&gt;, which creates a ghost instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;behind the Nginx+Traefik proxy (same as with &lt;code&gt;make qa&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;accessible through HTTPs (same as with &lt;code&gt;make qa&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;using MailGun to send emails ( &lt;strong&gt;new&lt;/strong&gt; )&lt;/li&gt;
&lt;li&gt;using a MariaDB instead of sqlite3 ( &lt;strong&gt;new&lt;/strong&gt; )&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because we are in a relative mindset (remember the philosophy?), we are going to compare our new set up, performance-wise, with the old one...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;define a new blog NAME for the exercise: &lt;code&gt;export NAME=fire-at-blog&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;spawn the instance with sqlite3: &lt;code&gt;make qa&lt;/code&gt; (we will use the default content to keep it simple)&lt;/li&gt;
&lt;li&gt;measure performance (and produce our first report): &lt;code&gt;make gatling&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--we1UbCMm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/with.sqlite3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--we1UbCMm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/with.sqlite3.png" alt="Ghost in A shell - part III : to Production, with performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;start again same blog using MariaDB: &lt;code&gt;make stop prod&lt;/code&gt; (you could also go for &lt;code&gt;make stop prod logs&lt;/code&gt; target to follow the logs and see when the instance is ready)&lt;/li&gt;
&lt;li&gt;measure performance again: &lt;code&gt;make gatling&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QXRrCeaK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/with-mariadb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QXRrCeaK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/with-mariadb.png" alt="Ghost in A shell - part III : to Production, with performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;compare the two reports written in the folder &lt;em&gt;./gatling-results&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We do not really care about the absolute values of any of the two reports. We are interested by the differences. Whatever the expectations were.. we now have the detailed figures with one command. And we can access directly in our browser the reports.&lt;/p&gt;

&lt;p&gt;For illustation purpose, I have only shown the high level figures for requests and active... but you have much more (thank you &lt;a href="https://gatling.io"&gt;gatling&lt;/a&gt;!)&lt;/p&gt;

&lt;p&gt;Of course, that will not alone make a decision. Let's not forget that we also want ease of maintenance (sqlite3 might be better here), and reliability (MariaDB gets several good points there)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Most important: we have a clear view on the performance of one solution vs the other&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  &lt;code&gt;make gatling&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;The magic in the section above come from the command &lt;code&gt;make gatling&lt;/code&gt;, which is actually quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gatling:
    docker run -it --rm \
        -v $(shell pwd)/etc/gatling-conf.scala:/opt/gatling/user-files/simulations/ghost/GhostFrontend.scala \
        -v $(shell pwd)/gatling-results:/opt/gatling/results \
        -e JAVA_OPTS="-Dusers=${GATLING_USERS} -Dramp=${GATLING_RAMP} -DbaseUrl=${GATLING_BASE_URL}" \
        --network=proxy \
        denvazh/gatling -m -s ghost.GhostFrontend

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using a docker container which encapsulate all the (Java) layers needed by gatling.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-v $(shell pwd)/etc/gatling-conf.scala ...&lt;/code&gt; and mounts a few volumes for input (scenario file)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-v $(shell pwd)/gatling-results ...&lt;/code&gt; mounts the volume for the output.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-e JAVA_OPTS="-Dusers=${GATLING_USERS} ...&lt;/code&gt; adapts the script according to the environment variables (more details below)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--network=proxy&lt;/code&gt; allows the container to directly load-test its beloved siblings (e.g. the blog instances)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scenario file, &lt;em&gt;./etc/gatling-conf.scala&lt;/em&gt;, has 4 sequences (self explicit):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;atOnceUsers(nbUsers),
constantUsersPerSec(5) during (myRamp seconds),
rampUsers(nbUsers) over (myRamp seconds),
heavisideUsers(10) over (myRamp seconds)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that two variables (&lt;code&gt;nbUsers&lt;/code&gt; &amp;amp; &lt;code&gt;myRamp&lt;/code&gt;) are available to give you a bit of flexibility in the loads you are expecting. Default values are provided in the Makefile and can ve overridden as usual.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GATLING_BASE_URL?=${PROTOCOL}://${NAME}:2368/${URI}
GATLING_USERS?=3
GATLING_RAMP?=5

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the galing container which targets the container that runs your blog instance. (hence the &lt;code&gt;--network=proxy&lt;/code&gt; parameter in the gatling command). Here is a more realistic example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GATLING_BASE_URL=https://your.qa.com/blog GATLING_USERS=20 make gatling&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You might wonder what will be the "full URL(s)" since we make use of a "..._BASE_URL". You will have to change the configuration file here (or use another one). They are so far coded as the following array, which is use as many time as necessary (thanks to the last bit &lt;code&gt;.circular&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  val uriFeeder = Array(
    Map("URIKey" -&amp;gt; s"welcome"),
    Map("URIKey" -&amp;gt; s"the-editor"),
    Map("URIKey" -&amp;gt; s"using-tags"),
    Map("URIKey" -&amp;gt; s"managing-users"),
    Map("URIKey" -&amp;gt; s"private-sites"),
    Map("URIKey" -&amp;gt; s"advanced-markdown"),
    Map("URIKey" -&amp;gt; s"themes")
  ).circular

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the way, we could have chosen some other strategy than &lt;code&gt;.circular&lt;/code&gt; (e.g. &lt;code&gt;random&lt;/code&gt;), but I have chosen a repeatable + predictable strategy here, for the purpose of being able to compare the results with a controlled scenario.&lt;/p&gt;

&lt;p&gt;Let's apply this knowledge to load-test our proxy&lt;/p&gt;

&lt;h1&gt;
  
  
  Playing a bit with Nginx
&lt;/h1&gt;

&lt;p&gt;Keeping in line with this methodology, we could have a closer look at the proxy (Nginx + Traefik) and compare our configuration with another one with NGinx cache... We will use the configuration &lt;em&gt;prod-stack/etc/000-proxy-with-cache.conf.sample&lt;/em&gt; for this purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proxy_cache_path /var/cache levels=1:2 keys_zone=STATIC:10m inactive=24h max_size=1g;

server {
  listen 80 default_server;
  listen [::]:80 default_server;
  server_name _;

  location / {
    proxy_pass http://traefik/;
    proxy_set_header Host $host;
    proxy_buffering on;
    proxy_cache STATIC;
    proxy_cache_valid 200 1d;
    proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky part is&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to continue playing around locally, while&lt;/li&gt;
&lt;li&gt;making gatling access the blog through NGinx, and not directly from his container to the other container...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this purpose, we are going to use the &lt;code&gt;GATLING_BASE_URL&lt;/code&gt; variable along with the internal IP of our computer. This will make the gatling container query the blog through the host (which means through Nginx and Traefik) instead of heading straight at the ghost container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Watch out: 127.0.0.1 will not work (neither localhost) since the container will query itself in that case, not the ghost instances.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's say our internal IP is 192.168.178.93... here is the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;run a first container without cache: &lt;code&gt;NAME=no-cache-blog make qa logs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;once launched, run a gatling container: &lt;code&gt;NAME=no-cache-blog GATLING_BASE_URL=http://192.168.178.93/no-cache-blog make gatling&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;activate Nginx cache in the prod stack
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd /your/path/to/prod-stak/etc/conf.d
$ rm 000-default.conf
$ cp ../000-proxy-with-cache.conf.sample 000-proxy-with-cache.conf

# at this point you can stop and start the stack again, 
# or simply go inside the nginx container and reload the conf:
$ docker exec -it nginx-entrypoint bash
root@bef39f4ad198:/# nginx -s reload
... signal process started

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;run a second container with cache: &lt;code&gt;NAME=with-cache-blog make qa logs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;once launched, run gatling again: &lt;code&gt;NAME=with-cache-blog GATLING_BASE_URL=http://192.168.178.93/with-cache make-blog gatling&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;look at the results (no cache on left hand side, with cache on right hand side)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e6pwu2TA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/with-or-without.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e6pwu2TA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://manu.breton.ch/blog/content/images/2018/06/with-or-without.png" alt="Ghost in A shell - part III : to Production, with performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Performances are better with cache (as expected), but content could now "lag" and that could impact negatively the user experience... On the other end it could also impact positively the ease of maintenance, allowing us minor downtime of the blog itself (if well cached).&lt;/p&gt;

&lt;p&gt;Again, the final choice will depend on you. At least, you will have metrics to compare the performances of the various solutions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Keep in mind, and avoid...
&lt;/h1&gt;

&lt;p&gt;Avoid changing multiple criteria when comparing performances.&lt;/p&gt;

&lt;p&gt;For instance, doing the following comparison is a bad idea :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;make dev&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;make qa&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;make prod&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;some performance&lt;/td&gt;
&lt;td&gt;other perf.&lt;/td&gt;
&lt;td&gt;yet-another perf.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The results are actually useless because you have multiple changes between the different sets of configurations: you are changing at the same time the DB, the proxy, and the email settings (potentially some cache layer). Therefore you will be blind of the individual effects of each of them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;You now can run three modes of ghost instances, for development, testing or production:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Remarks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;straightforward (local) installation if you simply wish to bridge a container port on your host (3001 by default)&lt;/td&gt;
&lt;td&gt;also known as &lt;code&gt;make&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make qa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The former being not really convenient if you wish to serve on standard ports (80 or 443), and if you want anyone to access your blog easily, this command sets up a traefik router&lt;/td&gt;
&lt;td&gt;recommended usage of &lt;a href="https://github.com/ebreton/prod-stack"&gt;prod-stack&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make prod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same as above, using a MariaDB instead of SQLite and an email provider (Mailgun)&lt;/td&gt;
&lt;td&gt;requires a (free) account at Mailgun&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The (more) important get-away of this blog though, is the simple command &lt;code&gt;make gatling&lt;/code&gt;, and using it to validate (small and well identified) changes along the way.&lt;/p&gt;

&lt;p&gt;A good practice would actually be to integrate the command in the continuous integration workflow, and to raise alerts if the performance goes down&lt;/p&gt;

&lt;p&gt;In the next blog, I will introduce a few helpers for the maintenance... in order to keep a bit of order in all the instances we can now spawn here and there&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>ghost</category>
      <category>productivity</category>
      <category>docker</category>
    </item>
    <item>
      <title>Ghost in A shell - part II : HTTPs is the norm</title>
      <dc:creator>Manu</dc:creator>
      <pubDate>Fri, 25 May 2018 20:36:11 +0000</pubDate>
      <link>https://dev.to/ebreton/ghost-in-a-shell---part-ii---https-is-the-norm-1jj4</link>
      <guid>https://dev.to/ebreton/ghost-in-a-shell---part-ii---https-is-the-norm-1jj4</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmanu.breton.ch%2Fblog%2Fcontent%2Fimages%2F2018%2F05%2Fimage-blog-2-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmanu.breton.ch%2Fblog%2Fcontent%2Fimages%2F2018%2F05%2Fimage-blog-2-3.png" alt="Ghost in A shell - part II : HTTPs is the norm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Overview of the context in three points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The objective of this series is to facilitate (automate) the spawning of Ghost blogs&lt;/li&gt;
&lt;li&gt;This blog comes after &lt;a href="https://dev.to/ebreton/ghost-in-a-shell---part-i--localhost-5he9"&gt;part I - localhost&lt;/a&gt;, where I focused on one's computer&lt;/li&gt;
&lt;li&gt;The targeted audience are developers/ops who wish to build / wipe out ghost instances in a breeze&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would you be interested, please take a seat and make yourself at home, I will try 1) to make your reading comfortable, 2) to lead you to the magic world where Ghosts appear from thin air... and 3) to give you the magic wand that will pop out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://my.domain.com/ghost-local" rel="noopener noreferrer"&gt;https://my.domain.com/ghost-local&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://my.domain.com/another-ghost" rel="noopener noreferrer"&gt;https://my.domain.com/another-ghost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://my.domain.com/name-it" rel="noopener noreferrer"&gt;https://my.domain.com/name-it&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Appetizer: the repo(s)
&lt;/h1&gt;

&lt;p&gt;In order to fulfil my promises, I will make use of two repositories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/ebreton/prod-stack" rel="noopener noreferrer"&gt;prod-stack&lt;/a&gt;, which I have already introduced in the first blog. This repo will act as router and proxy for free, including the discussions with Let's Encrypt&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ebreton/ghost-in-a-shell" rel="noopener noreferrer"&gt;ghost-in-a-shell&lt;/a&gt;, which basically is the material behind the blogs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By cloning those two guys, and setup a few variables, you will be able to create your ghost instances with our old pal &lt;code&gt;make&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick setup for prod-stack
&lt;/h2&gt;

&lt;p&gt;Just to save you a return ticket to the &lt;a href="https://dev.to/ebreton/ghost-in-a-shell---part-i--localhost-5he9"&gt;first blog&lt;/a&gt;, here are the few steps necessary to get the &lt;em&gt;prod-stack&lt;/em&gt; up and serving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone git@github.com:ebreton/prod-stack.git
Cloning into 'prod-stack'...
...
$ cd prod-stack
$ make
cp etc/traefik.toml.sample etc/traefik.toml
cp etc/db.sample.env etc/db.env
sed -i s/password/auMr9jsMrPnX0Oatb9j4yX2AYBN2jTQV/g etc/db.env
Generated etc/db.env
docker-compose pull
Pulling nginx-entrypoint ...
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the logs stabilize, you can &lt;code&gt;Ctrl-C&lt;/code&gt; to get back on the command line, and &lt;code&gt;docker ps&lt;/code&gt; to check that the stack is running.&lt;/p&gt;

&lt;p&gt;The Traefik dashboard will be available on &lt;a href="http://localhost:8081" rel="noopener noreferrer"&gt;http://localhost:8081&lt;/a&gt; "protected" by Basic Authentication with the user &lt;em&gt;test/test&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick setup for ghost-in-a-shell
&lt;/h2&gt;

&lt;p&gt;Well, you should have &lt;em&gt;make&lt;/em&gt;, &lt;em&gt;docker&lt;/em&gt; and the &lt;em&gt;prod-stack&lt;/em&gt; installed at this point.&lt;/p&gt;

&lt;p&gt;You are ready to spawn a blog on &lt;a href="http://localhost:3001" rel="noopener noreferrer"&gt;http://localhost:3001&lt;/a&gt; with &lt;code&gt;make&lt;/code&gt;, or on &lt;a href="http://localhost/ghost-local" rel="noopener noreferrer"&gt;http://localhost/ghost-local&lt;/a&gt; with &lt;code&gt;make traefik&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's see how to do better!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;from here on, running a container with &lt;code&gt;make&lt;/code&gt; will be called the &lt;strong&gt;standalone mode&lt;/strong&gt;. Running a container with &lt;code&gt;make traefik&lt;/code&gt; will be called the &lt;strong&gt;traefik mode&lt;/strong&gt; (how original!)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Starters: environment variables
&lt;/h1&gt;

&lt;p&gt;The two modes make use of environment variables, with default values defined in the (well commented) &lt;a href="https://github.com/ebreton/ghost-in-a-shell/blob/master/.env" rel="noopener noreferrer"&gt;&lt;em&gt;.env&lt;/em&gt;&lt;/a&gt; file.&lt;/p&gt;

&lt;p&gt;If you happen to delete this file, &lt;code&gt;make&lt;/code&gt; will complain&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ rm .env
$ make
.env file is missing
make: *** [check-env] Error 1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;em&gt;.env&lt;/em&gt; is found, you will be able to check its values with &lt;code&gt;make vars&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ make vars
# values that will be used to create the blog URL
  NAME=ghost-local
  PROTOCOL=http
  DOMAIN=localhost
  PORT=3001
  URI=ghost-local

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing the configuration of the instance is a matter of changing those variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;either directly in the &lt;em&gt;.env&lt;/em&gt; file&lt;/li&gt;
&lt;li&gt;or in your environment, e.g. adding &lt;code&gt;export NAME=blog&lt;/code&gt; in your terminal (or in your &lt;em&gt;.bashrc&lt;/em&gt; when it comes to some other variables that will vary less in time, e.g &lt;em&gt;PROTOCOL&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;or in the command line, e.g. &lt;code&gt;PORT=3002 make&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ export NAME=blog
$ PORT=3002 make vars
# values that will be used to create the blog URL
  NAME=blog
  PROTOCOL=http
  DOMAIN=localhost
  PORT=3002
  URI=ghost-local

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variables should be self explanatory, especially if you think &lt;em&gt;PROTOCOL://DOMAIN:PORT/URI&lt;/em&gt;, but it never hurts to spell the words, so here is a quick summary of the variables, with their default values and which mode uses them&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;default value&lt;/th&gt;
&lt;th&gt;used by standalone / traefik mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;for the docker container and the data folder&lt;/td&gt;
&lt;td&gt;ghost-local&lt;/td&gt;
&lt;td&gt;both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROTOCOL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;to build the URL&lt;/td&gt;
&lt;td&gt;http&lt;/td&gt;
&lt;td&gt;traefik&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DOMAIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;to build the URL&lt;/td&gt;
&lt;td&gt;localhost&lt;/td&gt;
&lt;td&gt;both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PORT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;to build the URL&lt;/td&gt;
&lt;td&gt;3001&lt;/td&gt;
&lt;td&gt;standalone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;URI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;to build the URL&lt;/td&gt;
&lt;td&gt;${NAME}&lt;/td&gt;
&lt;td&gt;traefik&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  The Main Dish: HTTPs
&lt;/h1&gt;

&lt;p&gt;So here we are, we wish to run an instance on &lt;a href="http://my.domain.com/my-blog" rel="noopener noreferrer"&gt;http://my.domain.com/my-blog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I guess the following command is not going to be too much of a shock for you:&lt;br&gt;&lt;br&gt;
&lt;code&gt;DOMAIN=my.domain.com URI=my-blog make traefik&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You might actually have jumped straight to &lt;a href="https://my.domain.com/my-blog" rel="noopener noreferrer"&gt;http*&lt;em&gt;S&lt;/em&gt;*://my.domain.com/my-blog&lt;/a&gt; with &lt;code&gt;PROTOCOL=https DOMAIN=my.domain.com URI=my-blog make traefik&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's fine, if you had also anticipated&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DNS configuration, i.e my.domain.com is already pointing to your server&lt;/li&gt;
&lt;li&gt;The traefik configuration, and in particular if you have setup your own email address in the &lt;em&gt;traefik.toml&lt;/em&gt; file as directed in prod-stack/&lt;a href="https://github.com/ebreton/prod-stack/blob/master/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;By the way, do not forget to change the Basic Authentication used to access Traefik dashboard (also pointed out in prod-stack/&lt;a href="https://github.com/ebreton/prod-stack/blob/master/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this case, you could actually &lt;code&gt;export PROTOCOL=https&lt;/code&gt; and &lt;code&gt;export DOMAIN=my.domain.com&lt;/code&gt; in your environment, and simply run &lt;code&gt;URI=my-other-blog make traefik&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you hadn't anticipated... well... you now know what to do :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set DNS config at your preferred provider&lt;/li&gt;
&lt;li&gt;Adapt Traefik config in prod-stack&lt;/li&gt;
&lt;li&gt;Ease your process by adding some variables to your environment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a bonus, you can also force the redirection of http to http*&lt;em&gt;s&lt;/em&gt;* (and raw &lt;em&gt;domain.com&lt;/em&gt; to &lt;em&gt;&lt;a href="http://www.domain.com" rel="noopener noreferrer"&gt;www.domain.com&lt;/a&gt;&lt;/em&gt; as an extra bonus). This is done by Nginx, and through the &lt;a href="https://github.com/ebreton/prod-stack/blob/master/etc/002-redirect-to-https.conf.sample" rel="noopener noreferrer"&gt;configuration sample&lt;/a&gt; in the repo, which you just have to move to the &lt;em&gt;./etc/conf.d&lt;/em&gt; folder, and then restart the stack&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd /path/to/prod-stack
$ mv ./etc 002-redirect-to-https.conf.sample ./etc/conf.d/002-redirect-to-https.conf
$ make
...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing all of this will&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;redirect &lt;a href="http://domain.com/blog" rel="noopener noreferrer"&gt;http://domain.com/blog&lt;/a&gt; to &lt;a href="https://www.domain.com/blog" rel="noopener noreferrer"&gt;https://www.domain.com/blog&lt;/a&gt; (which go well beyond our initial objectives, but hey! that's nice and free, and optional)&lt;/li&gt;
&lt;li&gt;get a certificate from Let's Encrypt for &lt;a href="http://www.domain.com" rel="noopener noreferrer"&gt;www.domain.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;serve the blog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there is more...&lt;/p&gt;

&lt;h1&gt;
  
  
  Digestive: helpers
&lt;/h1&gt;

&lt;p&gt;We have already used &lt;code&gt;make vars&lt;/code&gt; that listed the values of the environment variables.&lt;/p&gt;

&lt;p&gt;There is a few other helpers command, which are listed in the table below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Variables used&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make vars&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display the values of all env vars&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make ps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Display the running containers&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make shell&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Connect to given Ghost container&lt;/td&gt;
&lt;td&gt;NAME&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make logs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tail and follow the logs of given Ghost container&lt;/td&gt;
&lt;td&gt;NAME&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;make stop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop given Ghost container&lt;/td&gt;
&lt;td&gt;NAME&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is really few to say here, they are just convenient wrappers around standard docker commands.&lt;/p&gt;

&lt;h1&gt;
  
  
  Dessert (conclusion)
&lt;/h1&gt;

&lt;p&gt;I must admit that I am quite satisfied with the outcome of this second blog.&lt;/p&gt;

&lt;p&gt;We just have one tiny little bit of work to cope with now: the technical debt !&lt;/p&gt;

&lt;p&gt;More seriously, the choices made so far are shortcuts for most of them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ghost is running in development mode,&lt;/li&gt;
&lt;li&gt;and still uses &lt;em&gt;sqlite&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Not to mention there is no monitoring neither backup of anything (by the way, do not forget that you have &lt;a href="https://ghost.org/features/" rel="noopener noreferrer"&gt;Ghost(Pro)&lt;/a&gt; to relieve you from those burdens).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is therefore a very appropriate time for a very big red disclaimer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;do NOT use this stack in production&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Next?
&lt;/h1&gt;

&lt;p&gt;I will look more deeply into Ghost setup in next blog: its running mode and the DB to start with...&lt;/p&gt;

</description>
      <category>ghost</category>
      <category>productivity</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Ghost in A shell - Part I : localhost</title>
      <dc:creator>Manu</dc:creator>
      <pubDate>Thu, 24 May 2018 20:38:16 +0000</pubDate>
      <link>https://dev.to/ebreton/ghost-in-a-shell---part-i--localhost-5he9</link>
      <guid>https://dev.to/ebreton/ghost-in-a-shell---part-i--localhost-5he9</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmanu.breton.ch%2Fblog%2Fcontent%2Fimages%2F2018%2F05%2Ffeatured-image-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmanu.breton.ch%2Fblog%2Fcontent%2Fimages%2F2018%2F05%2Ffeatured-image-4.png" alt="Ghost in A shell - Part I : localhost"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last blog from the Foundation Ghost, "&lt;a href="https://blog.ghost.org/5/" rel="noopener noreferrer"&gt;After 5 years and $3M, here's everything we've learned from building Ghost&lt;/a&gt;", was an eye-opener for me.&lt;/p&gt;

&lt;p&gt;Not only the company model: a Foundation whose owners will &lt;strong&gt;not&lt;/strong&gt; be able to sell, on purpose; but especially the actual love behind the product:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;when we do anything you can be absolutely certain it's in the sole interest of building the best product we can for the right reasons.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Coming from WordPress, having played with Jekyll and Hugo, I could only take the bait to try Ghost. Therefore, I happily threw myself into the doc and the installation process to get a taste of the product.&lt;/p&gt;

&lt;h1&gt;
  
  
  Objective
&lt;/h1&gt;

&lt;p&gt;The overall objective of this (short) series of blogs is to be able to spawn easily multiple (independent) ghost blogs on one's beloved server.&lt;/p&gt;

&lt;p&gt;It's intended for developers/ops that wish to put their hands into the mechanics of Ghost, and need to easily spawn/kill blogs here and there.&lt;/p&gt;

&lt;p&gt;I will not go through the code of the blog itself, neither on the topics of writing blogs, nor creating a theme. I will limit my sharing to the installation-side of Ghost, and how to facilitate/automate the process.&lt;/p&gt;

&lt;p&gt;Would you &lt;strong&gt;not&lt;/strong&gt; feel like a devops at the end of the day, you might find better value in the &lt;a href="https://ghost.org/" rel="noopener noreferrer"&gt;Ghost(Pro)&lt;/a&gt; service, which will do a much better job of hosting your blogs, and keeping them running and up to date.&lt;/p&gt;

&lt;p&gt;In this first (of a few) blogs, I will focus on the installation of instances on localhost. The purpose is to set up locally a sandbox, as similar as possible as what we will ultimately have on production (hopefully).&lt;/p&gt;

&lt;h1&gt;
  
  
  Following the path
&lt;/h1&gt;

&lt;p&gt;There I went on my way, clicking on the &lt;a href="https://ghost.org/developers/" rel="noopener noreferrer"&gt;Developer &amp;gt; Documentation&lt;/a&gt; link of the Ghost website, scrolling a bit down, and finding the expected entry point for setting up things:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmanu.breton.ch%2Fblog%2Fcontent%2Fimages%2F2018%2F05%2Fsetup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmanu.breton.ch%2Fblog%2Fcontent%2Fimages%2F2018%2F05%2Fsetup.png" alt="Ghost in A shell - Part I : localhost"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After clicking on the Setup box, I had the choice between "&lt;em&gt;I want to install Ghost on my own server&lt;/em&gt;" and "&lt;em&gt;I want to try Ghost out quickly&lt;/em&gt;". I thought I was very lucky and I clicked on the second option: &lt;a href="https://docs.ghost.org/v1.0.0/docs/install-local" rel="noopener noreferrer"&gt;local install guide&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The TL;DR version of local installs, is you want to make use of Ghost CLI's super handy command: &lt;code&gt;ghost install local&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From there I stumbled on&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current configuration of my environment&lt;/li&gt;
&lt;li&gt;its Node version&lt;/li&gt;
&lt;li&gt;and my lack of knowledge of the JavaScript stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I quickly switched to one of my server running Ubuntu, where it was also quite painful to get all the versions aligned... At the end of my 10mins timebox, I could not make the ghost-cli execute itself properly.&lt;/p&gt;

&lt;p&gt;I (quickly) switched again for a Docker image, and there you go...&lt;/p&gt;

&lt;h1&gt;
  
  
  Thank you Docker!
&lt;/h1&gt;

&lt;p&gt;There is an official image (i.e. maintained by the DockerHub staff) on the docker hub, &lt;a href="https://hub.docker.com/_/ghost/" rel="noopener noreferrer"&gt;ghost&lt;/a&gt;, with a few examples of the commands available.&lt;/p&gt;

&lt;p&gt;Back on my Mac I created a simple Makefile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME?=ghost-local
DOMAIN?=localhost
PORT?=3001

dev: 
    # Simply start a ghost container making it directly available through $$PORT
    docker run --rm -d --name ${NAME} \
        -v $(shell pwd)/instances/${NAME}:/var/lib/ghost/content \
        -p ${PORT}:2368 \
        -e url=http://${DOMAIN}:${PORT} \
        ghost:1-alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;if/when copy-pasting, make sure to use tabs for the indentation (not spaces)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Running &lt;code&gt;make&lt;/code&gt; was the only thing left to get my blog running on &lt;a href="http://localhost:3001" rel="noopener noreferrer"&gt;http://localhost:3001&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;NAME=another PORT=3002 make&lt;/code&gt; runs a second blog available on &lt;a href="http://localhost:3002" rel="noopener noreferrer"&gt;http://localhost:3002&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker parameters
&lt;/h2&gt;

&lt;p&gt;A quick explanation on the arguments passed above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--rm&lt;/code&gt; will remove the container when it stops, thus preventing conflicts when spawning a new container with the same ${NAME}&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d&lt;/code&gt; will run the container in daemon mode&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--name ${NAME}&lt;/code&gt; will name your container. This one is optional, but it is nice to have the same name for the container and the data volume defined on the following line...&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-v $(shell pwd)/instances/${NAME}:/var/lib/ghost/content&lt;/code&gt; will mount the Ghost files locally on your file system into subfolder &lt;em&gt;./instances&lt;/em&gt;. That will persist data (and allow you to sneak into the code later on).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p ${PORT}:2368&lt;/code&gt; exposes the blog on the local ${PORT}&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-e url=http://${DOMAIN}:${PORT}&lt;/code&gt; tells ghost what URL will be used to access it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A bit of cleaning up
&lt;/h2&gt;

&lt;p&gt;Before next step, you can stop the containers with &lt;code&gt;docker stop ghost-local another&lt;/code&gt;. However, the data will not be lost thanks to the volumes.&lt;/p&gt;

&lt;p&gt;Moreover, they will be used again the next time you start a blog with the same name.&lt;/p&gt;

&lt;h1&gt;
  
  
  Thank you Traefik !
&lt;/h1&gt;

&lt;p&gt;My initial objective achieved, I went for the bonus...&lt;/p&gt;

&lt;h2&gt;
  
  
  The bonus
&lt;/h2&gt;

&lt;p&gt;The first one was to be able to serve on port 80, and to have the blogs sit on different paths. Something like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://localhost/ghost-local" rel="noopener noreferrer"&gt;http://localhost/ghost-local&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://localhost/another-ghost" rel="noopener noreferrer"&gt;http://localhost/another-ghost&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The "prod-stack" companion
&lt;/h2&gt;

&lt;p&gt;I have my so humbly called &lt;a href="https://github.com/ebreton/prod-stack" rel="noopener noreferrer"&gt;"prod-stack" repo&lt;/a&gt; that provides me with this kind of routing functionalities (among others). It is based on Traefik (and NGinx), and allows me to quickly set up a proxy, a DB and a memcached.&lt;/p&gt;

&lt;p&gt;It's running on my Mac, therefore making this solution free for me. I could only buy it :)&lt;/p&gt;

&lt;p&gt;For you dear reader, I have taken advantage of this blog to open my prod-stack on github, and I also have done a bit of curating with the Readme (even though it is still more a draft than a real documentation).&lt;/p&gt;

&lt;p&gt;Here is the fast-track setup to pull and run all docker containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone git@github.com:ebreton/prod-stack.git
Cloning into 'prod-stack'...
...
$ cd prod-stack
$ make
cp etc/traefik.toml.sample etc/traefik.toml
cp etc/db.sample.env etc/db.env
sed -i s/password/auMr9jsMrPnX0Oatb9j4yX2AYBN2jTQV/g etc/db.env
Generated etc/db.env
docker-compose pull
Pulling nginx-entrypoint ...
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the logs stabilize, you can &lt;code&gt;Ctrl-C&lt;/code&gt; to get back on the command line, and &lt;code&gt;docker ps&lt;/code&gt; to check that the stack is running.&lt;/p&gt;

&lt;p&gt;The Traefik dashboard will be available on &lt;a href="http://localhost:8081" rel="noopener noreferrer"&gt;http://localhost:8081&lt;/a&gt; with the user &lt;em&gt;test/test&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmanu.breton.ch%2Fblog%2Fcontent%2Fimages%2F2018%2F05%2Ftraefik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmanu.breton.ch%2Fblog%2Fcontent%2Fimages%2F2018%2F05%2Ftraefik.png" alt="Ghost in A shell - Part I : localhost"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please, do not hesitate to feedback thanks to &lt;a href="https://github.com/ebreton/prod-stack/issues" rel="noopener noreferrer"&gt;issues&lt;/a&gt; if you felt the curiosity to try it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding &lt;code&gt;make traefik&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;After making sure that Traefik is running, I added this command to the Makefile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;traefik:
    # Start a ghost container behind traefik on path $$NAME
    # Beware of --network used, which is the same one traefik should be using
    docker run --rm -d --name ${NAME} \
        -v $(shell pwd)/instances/${NAME}:/var/lib/ghost/content \
        -e url=http://${DOMAIN}/${NAME} \
        --network=proxy \
        --label "traefik.enable=true" \
        --label "traefik.backend=${NAME}" \
        --label "traefik.frontend.entryPoints=http" \
        --label "traefik.frontend.rule=Host:${DOMAIN};PathPrefix:/${NAME}" \
        ghost:1-alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parameters are similar to the ones of the previous command with the following changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ${PORT} is not exposed anymore since Traefik will handle the routing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--network=proxy&lt;/code&gt; launches the container in the same network as the one where Traefik is running (the values &lt;code&gt;proxy&lt;/code&gt; is arbitrary and comes from the default configuration of the "prod-stack")&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--label key=value&lt;/code&gt; are used by Traefik to create the appropriate route&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running &lt;code&gt;make traefik&lt;/code&gt; will now run a blog on &lt;a href="http://localhost/ghost-local" rel="noopener noreferrer"&gt;http://localhost/ghost-local&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;NAME=another-ghost make traefik&lt;/code&gt; runs a second blog available on &lt;a href="http://localhost/another-ghost" rel="noopener noreferrer"&gt;http://localhost/another-ghost&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;First of all&lt;/strong&gt; , I wanted to easily spawn Ghost blogs on my local machine,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://localhost:3001" rel="noopener noreferrer"&gt;http://localhost:3001&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://localhost:3002" rel="noopener noreferrer"&gt;http://localhost:3002&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The native documentation did not survive to my impatience, mainly because I knew it would be fast and smooth with Docker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secondly&lt;/strong&gt; , I wanted to use the standard port (80 to start with), and route the blogs according to their path,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://localhost/ghost-local" rel="noopener noreferrer"&gt;http://localhost/ghost-local&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://localhost/another-ghost" rel="noopener noreferrer"&gt;http://localhost/another-ghost&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was given for free with Traefik (and my "prod-stack") and I must admit I had a lot of fun playing with make. However, the result is still one for a developer, surely not ready for production (despite the name of the stack).&lt;/p&gt;

&lt;p&gt;Nevertheless, the stack can provide HTTPs thanks to Let's Encrypt, a DB and some easy way to manage all this... I will dive into this in the &lt;a href="https://dev.to/ebreton/ghost-in-a-shell---part-ii---https-is-the-norm-1jj4"&gt;next part&lt;/a&gt; to get something more robust... maybe&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://manu.breton.ch/blog" rel="noopener noreferrer"&gt;https://manu.breton.ch/blog&lt;/a&gt; ?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ghost</category>
      <category>productivity</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
