Buy My Book on Amazon Buy My Book Direct (PDF) GitHub Profile LinkedIn Profile

Build a Docker Image for Network Automation

Development environment for network autommation

By Shahzad Qadir, Posted on: Dec. 1, 2025, 9:37 a.m.

This post is part of my book on Full Stack Network Automation.

Buy My Book on Amazon Buy My Book Direct (PDF) GitHub Profile LinkedIn Profile


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

  1. Python (from Microsoft) - for syntax highlighting

  2. 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
  1. FROM statement pulls the base image to use, here we are using Ubuntu 22.04 as our base image.
  2. RUN executes operating system commands, In the first RUN we 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.
  3. By default Ubuntu 22.04 container image doesn't come with ifconfig, ping, or any other network related utilities that we commonly use. net-tools include ifconfig, netstat and other useful utilities. iputils-ping is used to install ping utility.
  4. We are installing pip package manager for Python, which makes installing Python packages and libraries very easy.
  5. We are installing paramiko and ansible. We will be using paramiko to 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.
  6. By default Ubuntu container doesn't come with SSH client (or server) pre-installed, so we have to install it as well.
  7. By default when we create containers from this image, container will run as root user. We are adding a user called script and creating it's home directory and .ssh directory.
  8. COPY instruction is used to copy files from host machine to the container. We are copying cisco_ssh file we created earlier to handle Cisco devices authentication.
  9. USER specifies the user to login.
  10. We have created /home/script/.ssh/known_hosts file and granted it correct permissions so when we try to login to a new device, we don't have issues writing to known_hosts.
  11. 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.

Buy My Book on Amazon Buy My Book Direct (PDF) GitHub Profile LinkedIn Profile