This post is part of my book on Full Stack Network Automation.
Chapter 1: Build Docker Image
We’ll start by building a Docker image that includes all the software needed for our automation project. To build a Docker image, we first need to install the Docker Engine on our machine.
I’ll be using Ubuntu 22.04 Desktop as the host machine.
Docker Installation
Linux - Debian based
If you are using a Debian-based Linux distribution such as Ubuntu, you can install Docker by following these steps:
Step 1 - Set up the APT repository
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
Step 2 - Install required packages
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Step 3 - Verify the Installation
You can verify installation using:
~$ docker --version
Docker version 29.0.0, build 3d4129b
Install Visual Studio (VS) Code
Before we start writing our Docker image, let’s install Visual Studio Code (VS Code). It’s the Integrated Development Environment (IDE) we’ll use to edit our code.
You can either install it via Snap or download the latest .deb package from code.visualstudio.com/download.
sudo snap install code --classic
If downloaded manually (my personal preference) - for Ubuntu download .deb file:
$ cd ~/Downloads
~/Downloads$ ls
code_1.106.0-1762878362_amd64.deb
~/Downloads$ sudo dpkg -i code_1.106.0-1762878362_amd64.deb
Once VS Code is installed, install the following extensions
-
Python (from Microsoft) - for syntax highlighting
-
Prettier (from prettier.io) - For code formatting
✳️ Note: I prefer not to use AI features and leave everything to default for VS Code itself and for extensions.
Git Setup
Most Debian-based distributions (including Ubuntu 22.04) come with Git pre-installed.
You can verify by running:
$ git --version
git version 2.34.1
Configure your Git identity:
$ git config --global user.name "Your Name"
$ git config --global user.email "Your Email"
We are good to go with version control; we will be using it in later chapters.
SSH config File
Some Cisco devices still use older, weaker ciphers that modern SSH clients no longer accept by default. To ensure we can SSH into those devices, let’s create a file named cisco_ssh and copy the following configuration into it. The hosts listed here represent the devices we’ll set up in the next chapter.
Let's begin by creating the automation directory within our home directory. We will later map this to our Docker container and do all development from this directory.
$ mkdir ~/automation && cd ~/automation
~/automation$ touch cisco_ssh
~/automation$ nano cisco_ssh
Copy this text to cisco_ssh:
# ~/automation/cisco_ssh
Host 10.10.99.1
KexAlgorithms +diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Ciphers aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc,aes128-ctr,aes192-ctr,aes256-ctr
HostkeyAlgorithms +ssh-rsa
Host 10.10.99.2
KexAlgorithms +diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Ciphers aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc,aes128-ctr,aes192-ctr,aes256-ctr
HostkeyAlgorithms +ssh-rsa
MACs +hmac-sha1,hmac-sha1-96,hmac-md5
Host 10.10.99.3
KexAlgorithms +diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Ciphers aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc,aes128-ctr,aes192-ctr,aes256-ctr
HostkeyAlgorithms +ssh-rsa
MACs +hmac-sha1,hmac-sha1-96,hmac-md5
Host 10.10.99.11
KexAlgorithms +diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Ciphers aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc,aes128-ctr,aes192-ctr,aes256-ctr
HostkeyAlgorithms +ssh-rsa
Host 10.10.99.12
KexAlgorithms +diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Ciphers aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc,aes128-ctr,aes192-ctr,aes256-ctr
HostkeyAlgorithms +ssh-rsa
Host 10.10.99.13
KexAlgorithms +diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Ciphers aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc,aes128-ctr,aes192-ctr,aes256-ctr
HostkeyAlgorithms +ssh-rsa
MACs +hmac-sha1,hmac-sha1-96,hmac-md5
Host 10.10.99.14
KexAlgorithms +diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Ciphers aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc,aes128-ctr,aes192-ctr,aes256-ctr
HostkeyAlgorithms +ssh-rsa
MACs +hmac-sha1,hmac-sha1-96,hmac-md5
Host 10.10.99.254
KexAlgorithms +diffie-hellman-group1-sha1
Ciphers aes128-cbc,aes192-cbc,aes256-cbc,3des-cbc
HostkeyAlgorithms +ssh-rsa
PubkeyAcceptedKeyTypes +ssh-rsa
MACs +hmac-sha1,hmac-sha1-96,hmac-md5
Docker Image
With Docker and VS Code installed, the next step is to write our Docker image and upload it to Docker Hub for easy access from any machine.
Dockerfile
Create a file called Dockerfile (no extension) which we will use to build the image.
$ touch Dockerfile
$ code . #(this will open VS Code in current directory)
Inside Dockerfile copy and paste the following
FROM ubuntu:22.04
RUN apt-get update && \
apt-get install -y \
python3.10 \
net-tools \
iputils-ping \
python3-pip \
openssh-client \
sshpass \
tree \
vim && \
rm -rf /var/lib/apt/lists/*
RUN pip install paramiko ansible ansible-pylibssh
RUN useradd -ms /bin/bash script
RUN mkdir /home/script/.ssh
COPY cisco_ssh /home/script/.ssh/config
RUN chown -R script:script /home/script/.ssh
USER script
RUN chmod 700 /home/script/.ssh
RUN touch /home/script/.ssh/known_hosts
RUN chmod 644 /home/script/.ssh/known_hosts
WORKDIR /automation
- FROM statement pulls the base image to use, here we are using Ubuntu 22.04 as our base image.
- RUN executes operating system commands, In the first
RUNwe are updating our base image and then installing Python 3.10 and other packages. The\is used to add a line break. We are sticking to a specific Python version for our automations so it won't break if there are any changes in the newer versions of Python. - By default Ubuntu 22.04 container image doesn't come with
ifconfig,ping, or any other network related utilities that we commonly use.net-toolsincludeifconfig,netstatand other useful utilities.iputils-pingis used to installpingutility. - We are installing
pippackage manager for Python, which makes installing Python packages and libraries very easy. - We are installing
paramikoandansible. We will be usingparamikoto connect to network devices and pull information which we will scrub and use for our purpose. Ansible is written in Python and is used for configuration management, we will work with Ansible in upcoming chapters. - By default Ubuntu container doesn't come with SSH client (or server) pre-installed, so we have to install it as well.
- By default when we create containers from this image, container will run as root user. We are adding a user called
scriptand creating it's home directory and.sshdirectory. - COPY instruction is used to copy files from host machine to the container. We are copying
cisco_sshfile we created earlier to handle Cisco devices authentication. - USER specifies the user to login.
- We have created
/home/script/.ssh/known_hostsfile and granted it correct permissions so when we try to login to a new device, we don't have issues writing toknown_hosts. - WORKDIR sets the default directory when we run container and log into it.
Now we can go ahead and build our docker image.
$ sudo docker build -t shahzadqadir/fullstack-auto:v1 .
docker build is command used to build docker images.
-t flag specifies the image tag, shahzadqadir is my Docker Hub username - use your username , it makes docker image unique on Docker Hub, v1 is the version number, and . specifies current directory as build context (Docker to look for Dockerfile in the current directory).
Docker uses a layered approach and doesn't download any layers it has already downloaded. But if you make any changes to a layer, it downloads that and all the subsequent layers again.
$ sudo docker image build -t shahzadqadir/fullstack-auto:v1 .
[+] Building 66.9s (16/16) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 615B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.8s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [ 1/11] FROM docker.io/library/ubuntu:22.04@sha256:09506232a8004baa32c47d68f1e5c307d648fdd59f5e7eaa42aaf87914100db3 0.0s
=> => resolve docker.io/library/ubuntu:22.04@sha256:09506232a8004baa32c47d68f1e5c307d648fdd59f5e7eaa42aaf87914100db3 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 31B 0.0s
=> [ 2/11] RUN apt-get update && apt-get install -y python3.10 net-tools iputils-ping python3-pip openssh-client vim && rm -rf /var/lib/apt/lists/* 38.8s
=> [ 3/11] RUN pip install paramiko ansible ansible-pylibssh 22.5s
=> [ 4/11] RUN useradd -ms /bin/bash script 0.3s
=> [ 5/11] RUN mkdir /home/script/.ssh 0.4s
=> [ 6/11] COPY cisco_ssh /home/script/.ssh/config 0.1s
=> [ 7/11] RUN chown -R script:script /home/script/.ssh 0.2s
=> [ 8/11] RUN chmod 700 /home/script/.ssh 0.2s
=> [ 9/11] RUN touch /home/script/.ssh/known_hosts 0.2s
=> [10/11] RUN chmod 644 /home/script/.ssh/known_hosts 0.2s
=> [11/11] WORKDIR /automation 0.1s
=> exporting to image 2.8s
=> => exporting layers 2.7s
=> => writing image sha256:628b9697fbdbe4c0f47c9159e6a71012b272ac2d655346022eca383d55160ae9 0.0s
=> => naming to docker.io/shahzadqadir/fullstack-auto:v1
We can view all docker images using docker images command:
$ sudo docker images
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
shahzadqadir/fullstack-auto:v1 17a8fd149af8 1.35GB 299MB
Our image is built, we can create a containers out of this image and that will have all the software that we instructed docker to install.
Docker Container
~/automation$ sudo docker run --rm -it -v ~/automation:/automation shahzadqadir/fullstack-auto:v1 bash
script@5e844a727ed0:/automation$
docker run is used to start a container
Flags
--rm tells docker to destroy container once we exit out of it, it's a good idea to include --rm so we don't have unused containers lying around.
-it to get an interactive shell of this container, bash specify the shell to use.
-v to map directory on the host to a directory inside the Docker container. We will always map our ~/automation directory we created earlier to /automation directory in docker container. We will develop locally in VS Code, access files in container and run from there as container will have all the software installed.
Now we can do basic checks:
script@dfe0e48f8c9f:/automation$ ansible --version
ansible [core 2.17.14]
config file = None
configured module search path = ['/home/script/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.10/dist-packages/ansible
ansible collection location = /home/script/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/local/bin/ansible
python version = 3.10.12 (main, Aug 15 2025, 14:32:43) [GCC 11.4.0] (/usr/bin/python3)
jinja version = 3.1.6
libyaml = True
script@dfe0e48f8c9f:/automation$ python3 --version
Python 3.10.12
script@dfe0e48f8c9f:/automation$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
ether 36:eb:75:57:5b:19 txqueuelen 0 (Ethernet)
RX packets 33 bytes 3944 (3.9 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3 bytes 126 (126.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
script@dfe0e48f8c9f:/automation$ ping bbc.com
PING bbc.com (151.101.0.81) 56(84) bytes of data.
64 bytes from 151.101.0.81 (151.101.0.81): icmp_seq=1 ttl=127 time=36.7 ms
Notice, docker has picked up an IP and has access to external domains without us doing any configurations for networking.
Exit out of docker container by typing exit - this will also remove container as we had used --rm flag when running this container. But the files created inside container will be available on host in ~/automation directory as we had mapped this directory to container.
Push Docker Image to Docker Hub
We got all the software we need installed in our docker container. We are ready to push it to docker hub. Go ahead and create an account on https://hub.docker.com/ and then proceed with following.
We will need to login and the push docker image to docker hub. On the host machine:
~/automation$ sudo docker login
~/automation$ sudo docker push shahzadqadir/fullstack-auto:v1
Map Local Drive to Container
Since we’ll use this image throughout the book, it’s helpful to map a local directory to the container. This way, any changes made are persisted locally.
Docker container loses all data when destroyed unless a local directory is mapped to a directory in container.
sudo docker run --rm -it -v ~/automation:/automation shahzadqadir/fullstack-auto:v1 bash
This post is part of my Book on Full Stack Network Automation.