How to create your own Docker image

I mentioned in my previous post that I’ll explain how to create your own Docker image and customize it however you’d like. While is great to just use an image from Docker Hub, it can be that you need some customized image to fit your needs. As said before, is not hard at all to create the image and worth knowing how to do it.

I’ll use for this tutorial a fresh Ubuntu 18.04 minimal installation. You can follow the same steps (or almost) using different Linux distro, Microsoft Windows or MacOS. The reason why I chose Ubuntu is simply because is the distro that I’m most familiar and enjoy working with.

For all steps below you need to be root or run the commands via sudo. So you’ll see either # at the begining of the command if you’re root or $ sudo if you pick to run it with elevated rights.

Install Docker

# apt install -y

A word of advice here. Be sure to have typed. If you miss the .io, the system will install a docker, but that’s a different package:

docker/bionic 1.5-1build1 amd64
  System tray for KDE3/GNOME2 docklet applications

You’ll end up with something that cannot be used for what we want to achieve, since the docker command isn’t even there.

You can test if the installation completed successfully by using the following command:

# service docker status

You should see something like this in the output:

Docker service successful status

Since this is a new installation, you’ll have no images, no containers, nothing.
You can check, just to be sure.

# docker image ls

The result should be:

Docker image ls return nothing

I’ll add at the end of the post some basic (and most important) Docker commands to get you started.

Pull Ubuntu 18.04 image – Optional step

This step is optional, but I’d advise to do it, just to test that everything is fine with your Docker installation In this case we’re going to use the official Ubuntu 18.04 minimal Docker image. If you want to read more about this image you can check the explanation on Ubuntu 18.04 minimal Docker image and check their repository on Docker Hub – Ubuntu.

# docker pull ubuntu:18.04

If everything goes well you should see a message ending with “Status: Downloaded newer image for ubuntu:18.04” :

Docker successful download of Ubuntu image

Time to run our first container:

# docker run -i -t ubuntu:18.04 /bin/bash

You should be now in container shell:

Docker container

Now that we tested you can type exit to leave the container.

Create Dockerfile

The Dockerfile is nothing more than a text document which contains all the commands a user could call on the command line to create an image.
A detailed explanation is beyond the scope of this post, but if you’d like to learn more, you can check the Docker Documentation – Dockerfile

Here is a sample that’s good to start with:

# My custom Ubuntu 18.04 with 
# various network tools installed
# Build image with:  docker build mycustomlinux01 .

FROM ubuntu:18.04
RUN apt-get update --fix-missing
RUN apt-get upgrade -y
RUN apt-get install -y software-properties-common
RUN apt-get install -y build-essential
RUN apt-get install -y net-tools mtr curl host
RUN apt-get install -y iputils-arping iputils-ping iputils-tracepath
RUN apt-get install -y iproute2
RUN apt-get install -y traceroute
RUN apt-get install -y tcpdump

A short explanation:

# – This is a comment, add here whatever you think is useful. I’ve picked the name “mycustomlinux01”, but you can add whatever you like.
FROM – is always your first instruction, because it names the base image you’re building your new image from.
MAINTAINER – is the creator of the Dockerfile.
RUN – instruction to run the specified command, in this case apt-get to install various packages

There are multiple instructions for setting environment variables like ADD, COPY, ENV, EXPOSE, LABEL, USER, WORKDIR, VOLUME, STOPSIGNAL, and ONBUILD. You can read all about them in the Docker Documentation – Dockerfile

Using RUN you can add whatever package you need in your custom image. The same like you would do on a regular Ubuntu installation.
Yes, all the packages above could have been added in one RUN line, but for the sake of better visibility I would suggest to have separate lines.

Create your custom Docker image

After you save the Dockerfile is time to create your image

# docker build -t mycustomlinux01 .

You’ll see a lot of output, the same like when you’re installing new packages in any Linux distro. When you see the following lines, you’ll know that the image was successful created:

Docker successful image creation

Let’s check if the image is listed using:

# docker image ls

You should see the mycustomlinux01 image listed:

List my Docker image

Since the image is created successful I’d suggest that you run a container using this image following the same steps like in the “Pull Ubuntu 18.04 image”

Basically that’s it, you just created your custom image.

As mentioned above, here is a list of commands that I find useful to have at hand when working with Docker containers.

List images:

# docker image ls

Start a container from an image:

# docker run -i -t ubuntu:12.04 /bin/bash

Using an ID (you get the ID from List image command):

# docker run -i -t 8dbd9e392a96 /bin/bash

List all containers:

# docker ps -a

List running containers:

# docker ps -l

Attach running container:

# docker attach “container ID”

Remove a container:

# docker rm “container ID”

Last but not least. If you liked my Ubuntu 18.04 Docker image customized for network engineers who wants to learn Python and you would like to install additional packages, here is the Dockerfile:

# Ubuntu 18.04 with Python, Paramiko, Netmiko, Ansible
# various other network tools installed and SSH activated
# Build image with:  docker build -t yotis/ubuntu1804-pfne .

FROM ubuntu:18.04
RUN apt-get update --fix-missing
RUN apt-get upgrade -y
RUN apt-get install -y software-properties-common
RUN apt-get install -y build-essential
RUN apt-get install -y openssl libssl-dev libffi-dev
RUN apt-get install -y net-tools mtr curl host socat
RUN apt-get install -y iputils-arping iputils-ping iputils-tracepath
RUN apt-get install -y iproute2
RUN apt-get install -y iptraf-ng traceroute
RUN apt-get install -y tcpdump nmap
RUN apt-get install -y iperf iperf3
RUN apt-get install -y python python-pip python-dev
RUN apt-get install -y python3 python3-pip python3-dev
RUN apt-get install -y openssh-client telnet
RUN apt-get install -y nano
RUN apt-get install -y netcat
RUN apt-get install -y socat
RUN pip install --upgrade pip
RUN pip install cryptography
RUN pip install paramiko
RUN pip install netmiko
RUN pip install pyntc
RUN pip install napalm
RUN apt-add-repository ppa:ansible/ansible
RUN apt-get update
RUN apt-get install -y ansible
RUN apt-get clean
VOLUME [ "/root" ]
WORKDIR [ "/root" ]
CMD [ "sh", "-c", "cd; exec bash -i" ]

Obviously there is more about Docker than is covered on this post. It wasn’t in my scope to make a detailed analyze of Docker, rather a cheatsheet on how to create your custom image. If you want to learn more there are plenty resources out there and a good starting point is the Docker website.

I hope you find this how-to useful. As always, if you need to add something or you have questions about, please use the Comments form to get in contact with me.

IPsec VPN Mikrotik to Cisco

Not long ago I wrote an article on how to configure an IPsec VPN using Mikrotik and Linux devices. For today, I will replace the Linux device with a Cisco. I did test the entire construct in GNS3 integrated with Mikrotik.

The topology looks like this:

IPsec VPN Mikrotik Cisco

The red line represent the IPsec VPN tunnel.
Please note the used IP addresses. In this way the below configuration will be easier to understand.

Mikrotik Configuration

1. Firewal rules

By default, the Mikrotik comes with the INPUT channel that drop the connection incoming on ether1-gateway (which is the WAN interface). You need to be sure that at least the IPsec packets are able to be accepted inbound on the WAN interface, so the below rules needs to be placed before the rule dropping packets (the Firewal rules are checked top-down)

On INPUT channel allow the following on the interface facing Internet
– Port 500/UDP
– Port 4500/UDP
– Proto 50
– Proto 51
It may be that you don’t need all these ports, but you can close them later. You can check logs if you want to troubleshoot.

On NAT channel, SRCNAT you need have the rule involving interesting traffic (local LAN subnets for example) before NAT masquerade.
You need to add a rule with ACCEPT source LOCAL_LAN ( in this example) destination REMOTE_LAN ( in this example).

On Console the configuration looks like this:


ip firewall filter add chain=input proto=ipsec-ah action=accept place-before=0
ip firewall filter add chain=input proto=ipsec-esp action=accept place-before=0
ip firewall filter add chain=input proto=udp port=500 action accept place-before=0
ip firewall filter add chain=input proto=udp port=4500 action accept place-before=0
ip firewall nat add chain=srcnat src-address= dst-address= action=accept place-before=0

2. The IPsec Proposal


IP > IPsec > Proposals

Name: MyProposal
Auth. Algorithm: sha1
Encr. Algorithm: aes-256 cbc
PFS Group: none


ip ipsec proposal add name=MyProposal auth-algorithms=sha1 enc-algorithms=aes-256-cbc pfs-group=none

3. The IPsec Policy


IP > IPsec > Policies

Protocol: all
Action: Encrypt
Level: require
IPsec protocols: esp
Tunnel: check
Proposal: MyProposal


ip ipsec policy add src-address= dst-address= protocol=all action=encrypt level=require ipsec-protocols=esp tunnel=yes sa-src-address= sa-dst-address= proposal=MyProposal

4. The IPsec Peer


IP > IPsec > Peers

Port: 500
Auth. Method: pre shared key
Passive: not checked
Secret: MYKEY
Policy Template Group: default
Exchange mode: main
Send Initial Contact: checked
NAT Traversal: checked
My ID: Auto - empty
Proposal Check: obey
Hash Algorithm: sha1
Encryptions Algorithm: aes-256
DH Group: modp1024
Generate policy: no


ip ipsec peer add address= port=500 auth-method=pre-shared-key secret=MY_KEY exchange-mode=main send-initial-contact=yes nat-traversal=yes proposal-check=obey hash-algorithm=sha1 enc-algorithm=aes-256 dh-group=modp1024 generate-policy=no

Cisco configuration

1. Crypto ISAKMP Policy

crypto isakmp policy 1
encr aes 256
authentication pre-share
group 2

You can specify also the hash as sha1, but this is the default method on Cisco, so no extra line will appear.

2. Crypto ISAKMP neighbor

crypto isakmp key MYKEY address no-xauth

3. Crypto IPsec transformation set

crypto ipsec transform-set MYTRANSFORMSET esp-aes 256 esp-sha-hmac
 mode tunnel

4. Crypto map

crypto map MYCRYPTOMAP 10 ipsec-isakmp
 description Mikrotik VPN
 set peer
 set transform-set MYTRANSFORMSET
 match address ACLTRAFF

5. Access-list for interesting traffic

ip access-list extended ACLTRAFF
 permit ip

6. Interface config

int fa1/0
 description Internet facing interface
 crypto map MYCRYPTOMAP

The settings (like encryption algorithm) can be tuned to fit your requirements.

If you have any questions or something is unclear please let me know in Comments.

Juniper, first steps after power-on the device

As you know from my previous posts, I’m trying to find time to gain some Juniper knowledge. During this “quest” I will add here some basic things about how to start working with Juniper devices. For now I know only the basics of Juniper configuration, but I hope that soon you’ll find here some more challenging scenarios.

I have a basic topology that you’ll find below. The scenario is already prepare to have some tasks which suppose integration between Juniper and Cisco environment.

Let’s assume that I did power on the two boxes J1 and J2 and now I’m connected to J1 through a console cable. After the boot sequence I’m left with something like this:

Tue Jun 12 11:46:06 UTC 2012

Amnesiac (ttyd0)


All platforms running the Junos OS have only the root user configured by default, without any password. Let’s introduce that username and see what’s happening:

login: root

--- JUNOS 9.4R2.9 built 2009-03-25 07:50:02 UTC
[email protected]% 

What I have now in front is actually the shell of the FreeBSD OS. JunOS is based on the FreeBSD OS. If you ever interacted with a Linux based system, then you can run here specific linux commands. For example:

[email protected]% ls
.snap           boot            jail            modules         sbin
COPYRIGHT       config          kernel          opt             staging
altconfig       data            libexec         packages        tmp
altroot         dev             mfs             proc            usr
bin             etc             mnt             root            var
[email protected]% 
[email protected]% 
[email protected]% 
[email protected]% ps u
root  1153  0.0  0.2  1492   936  v0  Is+  11:46AM   0:00.02 /usr/libexec/getty
root   941  0.0  0.4  2636  2176  d0- S    11:46AM   0:00.14 /usr/sbin/eventd -
root  1264  0.0  0.2  1676  1252  d0  Is   11:50AM   0:00.05 login [pam] (login
root  1265  0.0  0.5  3872  2744  d0  S    11:51AM   0:00.21 -csh (csh)
root  1289  0.0  0.2  1612   996  d0  R+   11:54AM   0:00.01 ps u

OK, you got my point. To get from the FreeBSD shell to JunOS CLI, you need to enter the following:

[email protected]% cli

What you see now is the Operational Mode. In this mode the user can run basic and troubleshooting commands (like traceroute, ping…). You can get a list of commands using the ? (question mark):

root> ?
Possible completions:
  clear                Clear information in the system
  configure            Manipulate software configuration information
  file                 Perform file operations
  help                 Provide help information
  monitor              Show real-time debugging information
  mtrace               Trace multicast path from source to receiver
  op                   Invoke an operation script
  ping                 Ping remote target
  quit                 Exit the management session
  request              Make system-level requests
  restart              Restart software process
  set                  Set CLI properties, date/time, craft interface message
  show                 Show system information
  ssh                  Start secure shell on another host
  start                Start shell
  telnet               Telnet to another host
  test                 Perform diagnostic debugging
  traceroute           Trace route to remote host

If you want to compare the Operational Mode is somehow like Privilege level 1 under Cisco CLI. Still I have the feeling that Operational Mode offer a wider area of commands and more powerful than Cisco CLI Privilege level 1. I may be mistaken.

All platforms running the Junos OS come with a factory-default configuration. All factory-default configurations
allow access using the root account without any password. Nevertheless to activate a configuration you have first to set the password root password.Factory-default configurations can vary from one platform family to another or even between the different models
within the same platform family.
My default configuration looks like:

root> show configuration 
version 9.4R2.9;
system {
    syslog {
        user * {
            any emergency;
        file messages {
            any notice;
            authorization info;
        file interactive-commands {
            interactive-commands any;

I this first post my target is to set the hostname of the Juniper devices. To accomplish this step, I need to go first into configuration mode:

root> configure 
Entering configuration mode
The configuration has been changed but not committed


and then set the hostname:

root# set system host-name J1 


If I look at the system prompt, it still shows root#, so it doesn’t quite seems to work. This is because I have to commit to activate the configuration:

root# commit 
    Missing mandatory statement: 'root-authentication'
error: commit failed: (missing statements)


Well, this didn’t work as expected. The most important thing that I learned when I started with Juniper is that before I can activate any configuration (commit) I need to set the password for the root user:

root# set system root-authentication plain-text-password              
New password:
Retype new password:


Let me try to commit one more time, after setting the root password:

root# commit 
commit complete

[email protected]# 

You can see that the prompt did change into [email protected]# (in my case this is [email protected]#). If you look again to the system configuration. I will exist the Configuration Mode and have another look at my config file:

[email protected]> show configuration 
## Last commit: 2012-06-12 12:38:22 UTC by root
version 9.4R2.9;
system {
    host-name J1;
    root-authentication {
        encrypted-password "$1$DKpYj/Nd$TVFTars5T2.oM3y5eyp520"; ## SECRET-DATA
    syslog {
        user * {
            any emergency;
        file messages {
            any notice;
            authorization info;
        file interactive-commands {
            interactive-commands any;

[email protected]> 

The host-name and root password appears now in the active configuration.

That’s it for today. Until next post, I will add the basic configuration for J2 and the Cisco, so I can go to basic interface configuration and connectivity check.

Cisco Easy VPN Router-to-Router

Cisco Easy VPN is not a new technology. Actually it is pretty old, but still used by many companies or people to connect remote site / remote workers to headquarter.

A few days ago I was looking to connect a remote site in a simple way but still secure and a colleagues suggested me to use Easy VPN. It supposed to be a simple configuration and it was after solving all issues that came into play.

First of all, I needed an Easy VPN Router(client) – to – Router(server). The other method is some client (PC) with software connection to Router / PIX / ASA / VPN Concentrator (Server). Something like this:


The idea is that behind the Client router, I will have a group of people who need to connect to the headquarter, so I don’t want each of them to use personal VPN connections. In search of possible configurations, I’ve found this Cisco configuration example. The only issue in that document is that the Easy VPN tunnel needs manual intervention to connect, which I want to avoid.

For those who need a quick and secure Easy VPN connection here is my sample configuration:


!! We define a new AAA model for authentication and authorization
!! for remote VPN clients
aaa new-model
aaa authentication login userauthen local
aaa authorization network groupauthor local
!! Generic username and password
username cisco password 0 cisco123
!! We configure a crypto isakmp policy. The number and encryption are your choice
crypto isakmp policy 3
encr 3des
authentication pre-share
group 2
!! We add a key and ! Important ! “save-password” command
!! “Save-password” allow client to save the password in an automatic vpn connection
!! scenario
crypto isakmp client configuration group vpngrp
key cisco123
!! The IPSec transform set; You can pick a stronger one like esp-aes 256, but
!! for this example will work fine
crypto ipsec transform-set myset esp-3des esp-sha-hmac
!! We get everything together in a crypto dynamic map
crypto dynamic-map dynmap 10
set transform-set myset
crypto map clientmap client authentication list userauthen
crypto map clientmap isakmp authorization list groupauthor
crypto map clientmap client configuration address respond
crypto map clientmap 10 ipsec-isakmp dynamic dynmap
!! Add the crypto map on the WAN interface or where your VPN tunnels will terminate
interface x/y
description WAN
crypto map clientmap


!! On the remote side we define an Easy VPN client
!! connect auto – means it will connect automatically
!! network-extension – connection between remote side LAN and server LAN will
!! not need NAT
!! peer is the VPN server IP address
!! xauth mode has to be local for auto connection without manual intervention
crypto ipsec client ezvpn ez
connect auto
group vpngrp key cisco123
mode network-extension
username cisco password cisco123
xauth userid mode local
!! Apply the already defined crypto to WAN interface
!! This will be automatically the Outside interface, even if you don’t
!! add the “outside” keyword at the end of the command
interface x/y
description WAN
crypto ipsec client ezvpn ez
!! Apply it on ALL L3 LAN interfaces that needs to communicate over VPN
!! more, you need to specifiy the keyword “inside”
interface x/y
description ANY L3 LAN interface (SVI / Physical)
crypto ipsec client ezvpn ez inside

To test if your tunnel is up, issue the following command on the EasyVPN client router

#show crypto ipsec client ezvpn
Easy VPN Remote Phase: 8

Tunnel name : TEST
Inside interface list: GigabitEthernet0/0, GigabitEthernet0/1
Outside interface: FastEthernet0/0
Current State: IPSEC_ACTIVE
Save Password: Allowed
Current EzVPN Peer:

As you can see the Current State shows IPSEC_ACTIVE

Other commands that will help you see if everything is ok (this can be run on client or server side)
#show crypto isakmp sa
! Look for the “state” (it has to be QM_IDLE) and
! “status” (has to be ACTIVE)

#show crypto ipsec sa
! Look for #pkts encaps and #pkts decaps; the decimal values should be close

I hope this will help you. If anything is unclear please ask in comments.


10 excuses you should avoid telling to your boss

I know, it’s not technical or related to Cisco, but it has everything to do with the industry in which we are network engineers are working. I found this great article by Justin James about the top 10 excuses a boss does not want to hear and I think it’s a good opportunity to share if with you. So look here what he’s saying:

“There are lots of reasons why a project might not be going well or may even fail. When your boss wants to know why, there is a world of difference between offering an excuse and providing a legitimate reason. In truth, most excuses only make your manager more upset and put the blame on you. Here are 10 common excuses that employees give their managers — and how you can turn them from weak excuses into a way of getting your supervisor to help you resolve the problems before your project is jeopardized.

1: I didn’t understand the assignment

Not every boss has great communication skills. And yes, having a manager who is not good at explaining what needs to be done makes life difficult. At the same time, using your boss’ inability to explain things as an excuse for not doing them just does not fly. If an assignment does not make sense, it’s your responsibility to find out what really has to happen. And if you find yourself in this situation more than once, it is a sign that you need to be extra careful when working with this particular person to get things fully understood.

2: The deadline was impossible

We all know this situation: A manager hands you an assignment with a deadline attached to it. You tell the manager that the deadline can’t be met and you’re told, “I don’t care; make it happen.” When the deadline is missed, you say, “But I told you the deadline was impossible!” and the boss is still angry. The disconnect here is that simply saying that the deadline is not possible is not good enough. As soon as the boss tells you to do it and you passively accept the ridiculous deadline, you make it your responsibility to meet it.

Your best defense is to negotiate a better deadline, and to do that, you need a project plan. The fact is, you always should be able to paint a picture of what a project will entail with some broad strokes anyway, and it is fairly easy to assign some rough estimates of the time to make each step happen. When you show your supervisor that even the most optimistic rough draft of a plan that omits a million minor details shows that it will take three months and they are demanding three weeks, guess what? It is now your manager’s responsibility to deal with the deadline issue. You have turned an opponent into an ally, and no sane boss can hold you accountable for the bad deadline anymore.”

Read the rest of the article here