DevOps
How to create a private Docker registry without https, SSL
Last Updated on
In this article, we will create a Linux private docker registry without https, SSL. few months ago when I was trying to set up a private docker registry without https there was no proper guide on the internet, thas why I decided to create this guide.
So let’s start.
Install Docker
first of all, we have to install docker in a Linux box which we are going to use as the docker registry.
you can install docker using this command:
in centos 7,RedHat
1 |
yum install docker |
in ubuntu
1 |
apt-get install docker.io |
then start the docker service:
1 |
systemctl start docker |
you can verify docker installation using this:
1 |
docker --version |

Run Docker Registry Image
now we can install the docker registry in this Linux box. actually registry is just a docker container.
so let’s pull it from the docker hub and run. Please check below, for example, docker run command.
1 2 3 4 |
docker run -d -p 5000:5000 --name linuxteacherregistry \ --restart always \ -e REGISTRY_STORAGE_DELETE_ENABLED=true \ registry:2 |
-p 5000:5000 –> this is the port mapping from your Linux box port 5000 to container port 5000.
–name linuxteacherregistry –> you can give any name you want for this
-e REGISTRY_STORAGE_DELETE_ENABLED=true –> This is an environment variable and will be valuable for you in the feature. we will discuss this later in this article.
registry:2 = docker registry image we are pulling from the docker hub.

now run the above command.it will download the registry image from the docker hub and create your docker registry within a minute. that’s the beauty of docker 🙂
you can verify it using:
docker ps or docker container list (its the same command but docker ps is the old command.)
1 |
$ docker ps | grep linuxteacherregistry |

you can grep it by using the name you provided earlier.
Congratulations your docker registry is up and running 🙂
Access docker registry without https/SSL
now your private docker registry is running. you can access the docker registry using your Linux box IP or localhost.
But it’s hard to memorize the IP address when we access the docker registry from other Linux boxes.
so now we are going to give a name to the registry.
Let’s add an entry to /etc/hosts file in the servers.
NOTE: you have to add this entry to all /etc/hosts files on the servers which access this docker registry.
1 |
vim /etc/hosts |
you can give any name you want. no need to be it a .com or valid domain.because our traffic is not going through the public internet.
so I’m giving “registry.linuxteacher.com”.

17.17.0.26 = replace it with your Linux box IP which is used to run the Docker registry
registry.linuxteacher.com = replace with any name you want.
Push to the private registry
Now let’s try to push an image to our registry.
first of all download a sample image from the docker hub to test.
1 |
docker pull ubuntu:16.04 |
Tag It
then you have to tag the image with your private docker registry name and port.
Example:
1 |
docker tag ubuntu:16.04 registry.linuxteacher.com:5000/ubuntu:16.04 |
replace the “registry.linuxteacher.com” with your registry name.

now docker knows the part before “/” is the docker registry name. otherwise, docker will try to push to the docker hub and will get an error message like “unauthorized: authentication required”
Push it.
lets push the image to our registry.
1 |
docker push registry.linuxteacher.com:5000/ubuntu:16.04 |
wooh.. you will get an error like a bellow because of docker try to push the image using HTPPS.
1 2 3 |
master $ docker push registry.linuxteacher.com:5000/ubuntu:16.04 The push refers to repository [registry.linuxteacher.com:5000/ubuntu:16.04] Get https://registry.linuxteacher.com:5000/v2/: http: server gave HTTP response to HTTPS client |
Disable HTTPS error message
To avoid the above error, you have to create a daemon.json file or edit that file. file location is this:
/etc/docker/daemon.json
then add bellow entry to it.
1 2 3 |
{ "insecure-registries" : ["registry.linuxteacher.com:5000"] } |
Replace the “registry.linuxteacher.com” with your private docker registry name.
NOTE: you have to add the above entry to all your Linux boxes that access this docker registry.
Reload Docker daemon
After that, you have to restart or reload the Docker daemon. otherwise change will not be affected.
1 |
systemctl Reload Docker |
Now you can push to your docker registry without an issue using the above push command.
1 |
docker push registry.linuxteacher.com:5000/ubuntu:16.04 |
Congratulations!! you have successfully created a private docker registry without https 🙂
when I was creating my registry I had a bunch of questions in my head like,
how to view images in the registry or delete images from registry or view tags of an image?
I think you have some questions like above in your head. 🙂
so let’s find the answers to the above questions from our next article.
Also, in the previous article, we discussed different between docker add vs copy vs volume. Give it a try 🙂
DevOps
Saltstack Tutorial for beginners [2019]
Last Updated on
What Is SaltStack?
Salt (Salt-Stack) is an infrastructure automation and management system written in Python. You’ll see through this introduction that it is a powerful tool. Please note that this tutorial is for beginners.
Installation
Let’s begin by installing it. I am using a Debian based distro (Ubuntu, Linux Mint ..), for other distros please check the installation manual (for this part).
For this blog post, I am going to install Master and Minion in the same server (localhost).
Master is the server and Minion is the target.
1 2 3 4 5 6 7 |
sudo apt-get install python-software-properties sudo add-apt-repository ppa:saltstack/salt sudo apt-get update sudo apt-get install salt-master sudo apt-get install salt-minion service salt-master start service salt-minion start |
We’re going for now to configure the (local) minion:
Search for master and tell it to use localhost or 127.0.0.1 or even the default gateway 10.0.0.1
and restart the service :
1 |
service salt-minion restart |
Salt works with a system of keys, it can tells you which Minion was accepted/rejected. To list all the Minion keys your Master knows about, type :
1 |
salt-key -L |
If the host is rejected, add it using -a :
1 |
salt-key -a 'hostname' |
Check that the Minion responds:
the ‘*’ refers to all Minions whose key is accepted. You can specify instead, the FQDN of your minion.
Usage
We’re going, for now, some usage examples:
For Nginx (Installation of the server and starting its service):
1 2 |
salt '*' pkg.install nginx salt '*' service.start nginx |
For vim (Installation of vim)
1 |
salt '*' pkg.install vim |
You can even see all of your minion servers’ disk usage:
Or list network interfaces:
1 |
salt '*' network.interfaces |
You can also execute a command
salt ‘*’ cmd.run ‘ls -l /etc’
To list the documentation you can type
1 |
salt '*' sys.doc | less |
Grains
Salt comes with an interface to derive information about the underlying system. This is called the grains interface because it presents salt with grains of information.
The grains interface is made available to Salt modules and components so that the right salt minion commands are automatically available on the right systems.
Let’s view all grains our minions (*):
1 |
salt '*' grains.ls |
To list grains data:
1 |
salt '*' grains.items |
As an example, if we want to match all CentOS and ping them, we can also use grains this way:
1 |
salt -G 'os:CentOS' test.ping |
To list all minions with 64-bit CPUs, and return number of CPU cores for each matching one:
1 |
salt -G 'cpuarch:x86_64' grains.item num_cpus |
Adding new grains
Grains can also be assigned within the minion configuration file.
Open :
1 |
/etc/salt/minion |
And search for “grains”
1 2 3 4 5 |
grains: roles: - webserver - memcache deployment: mydatacenter |
(As you can see, there is a list of grains like “deployment” that have the value mydatacenter. You can add your own grain. Make sure everything respects YAML language standards. )
or by adding the code below (without “grains:”) in
1 |
/etc/salt/grains |
Example:
1 2 3 4 |
roles: - webserver - memcache deployment: mydatacente |
If your favorited language is Python, you have the habit of using dicts, you can use it also as a grain :
1 2 3 4 5 6 7 8 |
auth: { 'host':'host.server.domain.tld', 'port':8900, 'users':{ 'web': {'login':'web', 'password':'login'}, 'dba': {'login':'dba', 'password':'pass'}, } } |
To query the last example :
1 |
salt "*" grains.get 'host' |
Output format
Changing the output format is easy:
1 |
salt myminion grains.item pythonpath --out=pprint |
“–out” could be also one of the values listed here.
Troubleshooting
As troubleshooting is always part of learning, to debug the master or the minion you can use:
1 2 |
salt-master -l debug salt-minion -l debug |
Let’s see what the port connectivity from the minion with the NC command.
(Remember that salt uses two ports, you can change them in master/minion configuration under /etc/salt/)
1 2 |
nc -v -z salt.master.ip 4505 nc -v -z salt.master.ip 4506 |
Debug with salt-call
We’re going to use salt-call with state.highstate if you’re not familiar with states, we’re going to explain them for later:
salt-call -l debug state.highstate
The verbose output could help you troubleshoot your problems. You only need patience.
Turn up logs
Salt can be quite chatty when you change the logging setting to debug:
1 |
salt-minion -l debug |
Foreground run
By not starting the minion in daemon mode (-d) one can view any output from the minion as it works:
1 |
salt-minion & |
Increase the default timeout value when running salt. For example, to change the default timeout to 60 seconds:
1 |
salt -t 60 |
Combine all:
1 2 |
salt-minion -l debug & # On the minion salt '*' state.highstate -t 60 # On the master |
Salt Authentification
We already spoke about keys but here is a reminder. We’re using two main functionality of salt-key command (on the master).
To list the keys that are on the master:
salt-key -L
The keys that have been rejected, accepted and pending acceptance are listed.
The easiest way to accept the minion key is to accept all pending keys:
1 |
salt-key -A |
Salt file roots
A base environment is required to house the top file.
In
1 |
/etc/salt/master |
you can add :
1 2 3 4 5 6 7 8 9 |
file_roots: base: - /srv/salt/ dev: - /srv/salt/dev/services - /srv/salt/dev/states prod: - /srv/salt/prod/services - /srv/salt/prod/states |
If you want to organize more as I did, you can create two subdirectories: states and pillars
1 2 |
/srv/salt/dev/states /srv/salt/dev/pillars |
and under each directory, you can create other subdirectories ( dev, integration, test, production ..etc).
In this blog post, I am going to speak about states, but not pillars. You can find good examples in the official documentation.
Salt States
If you consider working with salt, states are an important point to understand and it is not complicated.
As we already had configured “file_roots” which is “/srv/salt/”, we can move to the next simple example :
Example:
On the master, add the following code to
1 |
/srv/salt/top.sls |
1 2 3 4 5 |
#Code to add base: '*': - webserve |
“base” is an environment and it’s our default one. You can define a list of minion that will be attached to that environment
Explanation:
In the last example, all hosts are concerned ( ‘*’ ), and the state webserver will be applied to them.
Minions can be matched by glob, PCRE regular expression, or by grains.
If we modify the last example and use “grains” matching instead of just writing “*”, you’ll have to modify your code like this :
1 2 3 4 |
base: 'os:Debian': - match: grain - webserver |
os: Debian’ is the used grain to match host(s)
The last line tells Salt to use the sls called “webserver”.
Let’s keep the first example for the rest of this tutorial and continue to the second part (SLS).
Create our ‘sls’ file called ‘webserver.sls’. Write the next code to that file:
1 2 3 |
nginx: # ID declaration pkg: # state declaration - installed # function declaration |
ID declaration is a random name but unique, in our example, it is the name of the package that should be installed on our minions.
When you type :
1 |
salt '*' state.highstate |
Salt will install the package defined in “webserver.sls” on your target minions defined in “top.sls” file
Let’s move to another interesting part of salt: How can we manage a configuration file on a distant machine? This will be the main subject of the next post.
DevOps
How to build a Docker cron job Container easily [2019]
Last Updated on
In this tutorial, we’ll be setting up Cron jobs in Docker and discuss how to avoid a common pitfall.
Let’s Code It!
We’ll build a docker cron job that runs while other services on docker are running. This is particularly useful if we want to backup our databases.
Setting things up
Directory structure.
1 2 3 4 |
/docker-cron /node-app /cron-app docker-compose.yml |
Create a docker-cron
root directory.
Container #1: Node
To start we’ll setup a node
image that will act as a service that’s supposed to run while the cron job is running.
Generate a blank node app in docker-cron
:
1 |
express node-app |
Create a Dockerfile
in the node-app
directory:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
FROM node MAINTAINER Steve Jobs WORKDIR /src COPY . /src RUN npm install EXPOSE 3000 ENTRYPOINT ["npm", "start"] |
Common Pitfall
Running cron in the same container as a service.
The Docker ubuntu
image by default doesn’t run cron at runtime because it’s intended to run only one process at a time. Below is improperly attempting to run two processes simultaneously, something Docker doesn’t support:
1 |
CMD node app.js && cron -f |
The trick is splitting cron into its own separate container.
Container #2: Cron
We’ll setup an ubuntu
image that will run our cron job in a separate container. This image doesn’t come with cron
by default, so we’ll need to install it and do a general update. Create a cron-app
directory in the docker-cron
project folder.
Create mycron
in cron-app
that specifies your cron:
1 |
* * * * * /cron/do.sh >> /cron/cron.log 2>&1 |
Make sure you have a blank line at the end of mycron
This cron will run every minute.
Create do.sh
in cron-app
that specifies what your cron does:
1 2 |
#!/bin/sh echo "Cron is called." |
Create a Dockerfile
in the cron-app
directory with the following contents:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
FROM ubuntu MAINTAINER Steve Jobs WORKDIR /cron ADD mycron . ADD do.sh . RUN apt-get update RUN apt-get install cron RUN touch /cron/cron.log RUN chmod +x do.sh RUN chmod 0600 mycron RUN crontab -u root mycron ENTRYPOINT ["cron", "-f"] |
Docker Compose
Now we’ll bring the two images together with Docker Compose.
Create a docker-compose.yml
file in the root docker-cron
directory with the following contents:
1 2 3 4 5 6 7 8 |
version: '2' services: node: build: ./node-app ports: - "3000:3000" cron: build: ./cron-app |
Thank you for taking the time to read this tutorial. Check out our previous article about docker add vs copy. Also, Your feedback will be appreciated.
DevOps
Docker ADD vs COPY vs VOLUME – [2019]
Last Updated on
Let’s Learn Docker ADD vs COPY vs VOLUME with Simple Examples :
We’ll learn in this tutorial the differences between Docker ADD vs COPY vs VOLUME when configuring Dockerfiles.
They all accomplish the same task (among other capabilities): moving files from the host computer to a Docker container.
VOLUME
VOLUME
is different from COPY
and ADD
because it creates a mount point that the host operating system can interact with.
1 |
VOLUME /Users/sjobs/Documents/cool-project /var/www |
This command syncs the Docker container’s /var/www
directory with the host OS’s cool-project
directory. When any change is made to cool-project
within the host OS it is immediately made available to the docker container mounting that directory and vice versa.

Using VOLUME
is especially useful for development environments where frequent modifications to the code are being made. For example, running nodemon
on a Docker container with a VOLUME
allows you to make changes to your code that are immediately reflected as if Docker were invisible.
COPY
COPY
accomplishes everything that VOLUME
accomplishes, but does it during build time. This is necessary for configuring containers with modified config files with services such as httpd, Nginx, MongoDB, etc… For example when configuring an Nginx container modifying the nginx.conf
file is an operation that must be done with COPY
.
One setback to COPY
is that it makes only the container that was copied to aware of the contents. Meaning we cannot access the contents on our host computer, or on other running docker containers, something VOLUME
does.

ADD
ADD
is exactly the same as COPY
except for one distinct difference. ADD
can fetch content from a URL and it will not extract the content, only downloading to the specific location. Check the bellow example.
1 |
ADD https://www-us.apache.org/dist//httpd/httpd-2.4.41.tar.gz /linuxteacher/from_url |

If the local content is recognized as compressed, will uncompress the contents. Check the bellow example.
1 |
ADD abc.tar.gz /linuxteacher/from_local |

It has the same setbacks that COPY
has.
This is the Dockerfile we used for the above Examples.

What do I do?
I use Volumes by default whenever I can so that I don’t have to rebuild my Docker container every time there’s a modification to the code.
So this is the ending of the Docker ADD vs COPY vs VOLUME Article. we will appreciate your feedback and check out our new article about mail Command in Linux.
-
Linux6 months ago
mail Command in Linux/Unix with 10+ Examples [2019]
-
Linux6 months ago
Grep Command In Unix/Linux with 25+ Examples [2019]
-
Linux6 months ago
Find command in Unix/Linux with 30+ Examples [2019]
-
Linux6 months ago
10+ Network Commands in Linux for Troubleshooting [2019]
-
DevOps3 months ago
Docker ADD vs COPY vs VOLUME – [2019]
-
DevOps2 months ago
Saltstack Tutorial for beginners [2019]
-
Linux3 months ago
Whereis command in Linux with 10+ Examples [2019]
-
DevOps3 months ago
How to build a Docker cron job Container easily [2019]