Skip to content

Deploying a container

In this section, we'll discuss how to create a workflow script to deploy and update docker containers running in your IoT Edge Linux devices. We'll also discuss how to rollback to the last working version of the docker container image, on failure to update a container. Docker images could be pulled from your private registry in AWS ECR, Docker Hub, GitHub Container Registry, GitLab Container Registry, or any other private container registry.

SocketXP OTA update workflow and deployment

You should be familiar with the SocketXP OTA update workflow and deployment process. Please read the previous section on OTA Update to learn more about SocketXP OTA update workflow and deployment before you read this section.

Docker container update - Workflow Script

Here is a sample Workflow script (written in Python and Shell Script) to update Docker containers running in your IoT Edge Linux Devices. Please feel free to modify or update the script to suit your requirements.

import os
import subprocess
import sys
from datetime import datetime
import time

# Set AWS credentials
AWS_ACCESS_KEY_ID = "ABCDEFGHIJKLMNOPQ"
AWS_SECRET_ACCESS_KEY = "AbcDeFgh1234pqRstUvW6789xyZ"
AWS_REGISTRY_ADDRESS = "123456789012.dkr.ecr.us-east-2.amazonaws.com"

# AWS (Elastic Container Registry) ECR login/logout commands
AWS_ECR_LOGIN_COMMAND = f"docker login --username {AWS_ACCESS_KEY_ID} --password-stdin  {AWS_REGISTRY_ADDRESS}"
AWS_ECR_LOGOUT_COMMAND = f"docker logout {AWS_REGISTRY_ADDRESS}"

# Set Docker Hub credentials
DOCKER_HUB_USERNAME = "dockerhub-username"
DOCKER_HUB_ACCESS_TOKEN = "75fa718c-9ec3-11ec-b909-0242ac120002"

# Docker Hub container registry login/logout commands
DOCKER_HUB_LOGIN_COMMAND = f"docker login --username {DOCKER_HUB_USERNAME} --password-stdin"
DOCKER_HUB_LOGOUT_COMMAND = "docker logout"


def debug_log(msg: str):
    print(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}: {msg}")


def cleanup():
    debug_log("Cleaning up...")
    # Logout of the docker image registry: DockerHub or AWS ECR
    os.system(DOCKER_HUB_LOGOUT_COMMAND)
    # os.system(AWS_ECR_LOGOUT_COMMAND)
    os.remove("/usr/local/bin/my_script.py")
    debug_log("Clean up done!")


def debug_error_exit(msg: str):
    print(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}: Error: {msg}")
    cleanup()
    sys.exit(1)


def restore_from_backup(docker_run_command: str, container_name: str):
    debug_log(f"Stopping and removing failed container: {container_name}")
    os.system(f"docker rm --force {container_name}")

    debug_log(f"Restoring from backup: {docker_run_command}")
    if os.system(docker_run_command) == 0:
        debug_log(f"Docker run command executed: {docker_run_command}")
    else:
        debug_error_exit(f"Docker run command failed!: {docker_run_command}")

    # Sleep 30 seconds before checking the status
    time.sleep(30)
    container_running_status = subprocess.check_output(f'docker inspect --format "{{.State.Running}}" --type container "{container_name}"', shell=True).decode().strip()
    if container_running_status:
        debug_log(f"Container running status: {container_running_status}")
    else:
        debug_error_exit(f"Failed retrieving container running status!: {container_name}")

    if container_running_status == "true":
        debug_log(f"Restoration successful: {container_name}")
    else:
        debug_error_exit(f"Restoration failed: {container_name}")


if __name__ == "__main__":
    # Get the container name and the latest image name from the command line args
    container = sys.argv[1]
    print(f"Container name: {container}")
    LATEST_IMAGE_NAME = sys.argv[2]
    print(f"Image name: {LATEST_IMAGE_NAME}")

    # Login to the docker image registry: DockerHub or AWS ECR
    os.system(f'echo "{DOCKER_HUB_ACCESS_TOKEN}" | ({DOCKER_HUB_LOGIN_COMMAND})')
    # os.system(f'echo "{AWS_SECRET_ACCESS_KEY}" | ({AWS_ECR_LOGIN_COMMAND})')

    # To update a list of containers, add a for loop to the following code block, as shown below:
    #
    # CONTAINERS=sys.argv[1]
    # LATEST_IMAGE_NAMES=sys.argv[2]
    #
    # for i in range(len(CONTAINERS)):
    #   container=CONTAINERS[i]
    #   LATEST_IMAGE_NAME=LATEST_IMAGE_NAMES[i]
    #   ...
    #   ...

    # Update Begin
    print(f"\n+++ Begin updating container: {container} +++\n")

    RUNNING_IMAGE_HASH = subprocess.check_output(f'docker inspect --format "{{.Image}}" --type container "{container}"', shell=True).decode().strip()
    if RUNNING_IMAGE_HASH:
        debug_log(f"Running container's image hash: {RUNNING_IMAGE_HASH}")
    else:
        debug_error_exit(f"Failed retrieving running container's image hash: {container}")

    # Pull in the latest version of the container and get the hash
    if os.system(f'docker pull "{LATEST_IMAGE_NAME}"') == 0:
        debug_log(f"docker pull of latest image successful: {LATEST_IMAGE_NAME}")
    else:
        debug_error_exit(f"docker pull of latest image failed: {LATEST_IMAGE_NAME}")

    # Get the latest image hash
    LATEST_IMAGE_HASH = subprocess.check_output(f'docker inspect --format "{{.Id}}" --type image "{LATEST_IMAGE_NAME}"', shell=True).decode().strip()
    if LATEST_IMAGE_HASH:
        debug_log(f"Latest image hash: {LATEST_IMAGE_HASH}")
    else:
        debug_error_exit(f"Failed retrieving the latest image's hash: {LATEST_IMAGE_NAME}")

    # Create a new container using the latest image, if the image is different
    if RUNNING_IMAGE_HASH != LATEST_IMAGE_HASH:
        debug_log(f"Updating container {container} with latest image {LATEST_IMAGE_NAME}")

        # Delete the running container first
        os.system(f'docker rm --force "{container}"')
        if RUNNING_IMAGE_HASH:
            debug_log(f"docker rm: {container}")
        else:
            debug_error_exit(f"docker rm failed!: {container}")

        # Create a new container using the latest image
        DOCKER_RUN_COMMAND = f'docker run -d --name "{container}"'
        if os.system(DOCKER_RUN_COMMAND + f' "{LATEST_IMAGE_NAME}"') == 0:
            debug_log(f'docker run command executed: {DOCKER_RUN_COMMAND} "{LATEST_IMAGE_NAME}"')
        else:
            debug_log(f'docker run command failed!: {DOCKER_RUN_COMMAND} "{LATEST_IMAGE_NAME}"')
        restore_from_backup(
        f'{DOCKER_RUN_COMMAND} "{RUNNING_IMAGE_HASH}"', f'{container}')

        # Sleep 30 seconds before checking the container status
        time.sleep(30)
        CONTAINER_RUNNING_STATUS = subprocess.check_output(f'docker inspect --format "{{.State.Running}}" --type container "{container}"', shell=True).decode().strip()
        if CONTAINER_RUNNING_STATUS:
            debug_log(f'Container running status: {CONTAINER_RUNNING_STATUS}')
        else:
            debug_log(f'Failed retrieving container running status!: {container}')
            restore_from_backup(f'{DOCKER_RUN_COMMAND} "{RUNNING_IMAGE_HASH}"', f'{container}')

        if CONTAINER_RUNNING_STATUS == "true":
            debug_log(f'Container update successful: {container}')
        else:
            debug_log(f'Container update failed: {container}')
            restore_from_backup(f'{DOCKER_RUN_COMMAND} "{RUNNING_IMAGE_HASH}"', f'{container}')

        # Remove the old image
        os.system(f'docker rmi "{RUNNING_IMAGE_HASH}"')
        if RUNNING_IMAGE_HASH:
            debug_log(f'docker rmi: "{RUNNING_IMAGE_HASH}"')
        else:
            debug_error_exit(f'docker rmi failed!: "{RUNNING_IMAGE_HASH}"')
    else:
        print(f'Running image same as the latest image. Not updating {container} with latest image {LATEST_IMAGE_NAME}')

    print(f"\n+++ End updating container: {container} +++\n")

    # Finally clean up
    cleanup()
Here is the Shell script version of the above Python script:

$ cat /usr/local/bin/my_script.sh

#!/bin/sh

#================================================
# Example Workflow Script:
# Docker Container Update Workflow Script 
#================================================

# Set AWS credentials
AWS_ACCESS_KEY_ID="ABCDEFGHIJKLMNOPQ"                                                 
AWS_SECRET_ACCESS_KEY="AbcDeFgh1234pqRstUvW6789xyZ" 
AWS_REGISTRY_ADDRESS="123456789012.dkr.ecr.us-east-2.amazonaws.com"                        

# AWS (Elastic Container Registry) ECR login/logout commands 
AWS_ECR_LOGIN_COMMAND="docker login --username ${AWS_ACCESS_KEY_ID} --password-stdin  ${AWS_REGISTRY_ADDRESS}"
AWS_ECR_LOGOUT_COMMAND="docker logout ${AWS_REGISTRY_ADDRESS}"

# Set Docker Hub credentials
DOCKER_HUB_USERNAME="dockerhub-username"
DOCKER_HUB_ACCESS_TOKEN="75fa718c-9ec3-11ec-b909-0242ac120002"

# Docker Hub container registry login/logout commands
DOCKER_HUB_LOGIN_COMMAND="docker login --username ${DOCKER_HUB_USERNAME} --password-stdin"
DOCKER_HUB_LOGOUT_COMMAND="docker logout"

# Log debugs
debug_log() {
    echo "$(date "%Y-%m-%d %H:%M:%S.%N"): $1"
}

# Clean Up
cleanup() {
   debug_log "Cleaning up..."
   # Logout of the docker image registry: DockerHub or AWS ECR
   $DOCKER_HUB_LOGOUT_COMMAND                                                                  
   # $AWS_ECR_LOGOUT_COMMAND                                                                  
   rm /usr/local/bin/my_script.sh  
   debug_log "Clean up done!"                                                     
}

# Log error and exit
debug_error_exit() {
    echo "$(date "%Y-%m-%d %H:%M:%S.%N"): Error: $1"
    cleanup
    exit 1
}

# Restore previously running container
restore_from_backup() {
    DOCKER_RUN_COMMAND=$1
    CONTAINER_NAME=$2

    debug_log "Stopping and removing failed container: ${CONTAINER_NAME}" 
    docker rm --force ${CONTAINER_NAME}

    debug_log "Restoring from backup: $DOCKER_RUN_COMMAND"
    eval $1
         if [ $? -eq 0 ]; then                                                                   
            debug_log "Docker run command executed: ${DOCKER_RUN_COMMAND}"                                      
         else                                                                                    
            debug_error_exit "Docker run command failed!: ${DOCKER_RUN_COMMAND}"
         fi
    # Sleep 30 seconds before checking the status
    sleep 30
    CONTAINER_RUNNING_STATUS="$(docker inspect --format "{{.State.Running}}" --type container "${CONTAINER_NAME}")"
        if [ $? -eq 0 ]; then                                                                         
            debug_log "Container running status: ${CONTAINER_RUNNING_STATUS}"                                               
        else                                                                                          
            debug_error_exit "Failed retrieving container running status!: ${CONTAINER_NAME}"                                                    
        fi 

    if [[ "$CONTAINER_RUNNING_STATUS" == "true" ]]; then
        debug_log "Restoration successful: ${CONTAINER_NAME}"
    else 
        debug_error_exit "Restoration failed: ${CONTAINER_NAME}"    
    fi  
}

#============================================
# Workflow Begins.
#============================================  

# Get the container name and the latest image name from the command line args                             
container=$1                                                         
echo "Container name: ${container}" 
LATEST_IMAGE_NAME=$2
echo "Image name: ${LATEST_IMAGE_NAME}"

# Login to the docker image registry: DockerHub or AWS ECR
echo "${DOCKER_HUB_ACCESS_TOKEN}" | $($DOCKER_HUB_LOGIN_COMMAND)
# echo "${AWS_SECRET_ACCESS_KEY}" | $($AWS_ECR_LOGIN_COMMAND)  


# To update a list of containers, add a for loop to the following code block, as shown below:
#
# CONTAINERS=$1
# LATEST_IMAGE_NAMES=$2
#
# for i in ${!CONTAINERS[@]}; do 
#   container=${CONTAINERS[i]}
#   LATEST_IMAGE_NAME=${LATEST_IMAGE_NAMES[i]}
#   ...
#   ...
# done

# Update Begin                                                                                                
echo "\n+++ Begin updating container: ${container} +++\n"
RUNNING_IMAGE_HASH="$(docker inspect --format "{{.Image}}" --type container "${container}")"       
    if [ $? -eq 0 ]; then                                                                         
    debug_log "Running container's image hash: ${RUNNING_IMAGE_HASH}"                                                   
    else                                                                                          
    debug_error_exit "Failed retrieving running container's image hash: ${container}"                                                      
    fi                                                                                            

# Pull in the latest version of the container and get the hash                                    
docker pull "${LATEST_IMAGE_NAME}"                                                              
    if [ $? -eq 0 ]; then                                                                         
    debug_log "docker pull of latest image successful: ${LATEST_IMAGE_NAME}"                                                   
    else                                                                                          
    debug_error_exit "docker pull of latest image failed: ${LATEST_IMAGE_NAME}"                                                        
    fi              

# Get the latest image hash                                                                              
LATEST_IMAGE_HASH="$(docker inspect --format "{{.Id}}" --type image "${LATEST_IMAGE_NAME}")"         
    if [ $? -eq 0 ]; then                                                                         
    debug_log "Latest image hash: ${LATEST_IMAGE_HASH}"                                                     
    else                                                                                          
    debug_error_exit "Failed retrieving the latest image's hash: ${LATEST_IMAGE_NAME}"                                                       
    fi                                                                                            

# Create a new container using the latest image, if the image is different                                             
if [[ "${RUNNING_IMAGE_HASH}" != "${LATEST_IMAGE_HASH}" ]]; then                                        
    debug_log "Updating container ${container} with latest image ${LATEST_IMAGE_NAME}"                                        

    # Delete the running container first                                                
    docker rm --force "${container}"                                                            
        if [ $? -eq 0 ]; then                                                                   
        debug_log "docker rm: ${container}"                                                         
        else                                                                                    
        debug_error_exit "docker rm failed!: ${container}"                                                        
        fi    

    # Create a new container using the latest image
    DOCKER_RUN_COMMAND="docker run -d --name ${container}"  
    eval "${DOCKER_RUN_COMMAND} ${LATEST_IMAGE_NAME}"                                                               
        if [ $? -eq 0 ]; then                                                                   
        debug_log "docker run command executed: ${DOCKER_RUN_COMMAND} ${LATEST_IMAGE_NAME}"                                      
        else                                                                                    
        debug_log "docker run command failed!: ${DOCKER_RUN_COMMAND} ${LATEST_IMAGE_NAME}" 
        restore_from_backup "$(${DOCKER_RUN_COMMAND} ${RUNNING_IMAGE_HASH})" "${container}"
        fi 

    # Sleep 30 seconds before checking the container status
    sleep 30
    CONTAINER_RUNNING_STATUS="$(docker inspect --format "{{.State.Running}}" --type container "${container}")"
        if [ $? -eq 0 ]; then                                                                         
          debug_log "Container running status: ${CONTAINER_RUNNING_STATUS}"                                               
        else                                                                                          
          debug_log "Failed retrieving container running status!: ${container}" 
          restore_from_backup "${DOCKER_RUN_COMMAND} ${RUNNING_IMAGE_HASH}" "${container}"
        fi

    if [[ "$CONTAINER_RUNNING_STATUS" == "true"  ]]; then
        debug_log "Container update successful: ${container}"
    else
        debug_log "Container update failed: ${container}"
        restore_from_backup "${DOCKER_RUN_COMMAND} ${RUNNING_IMAGE_HASH}" "${container}"
    fi

    # Remove the old image                                                                                     
    docker rmi "${RUNNING_IMAGE_HASH}"                                                               
        if [ $? -eq 0 ]; then                                                                   
        debug_log "docker rmi: ${RUNNING_IMAGE_HASH}"                                                    
        else                                                                                    
        debug_error_exit "docker rmi failed!: ${RUNNING_IMAGE_HASH}"                                                       
        fi                                                                                      
else                                                                                          
  echo "Running image same as the latest image. Not updating ${container} with latest image ${LATEST_IMAGE_NAME}"                
fi

echo "\n+++ End updating container: ${container} +++\n"

# Finally clean up
cleanup             

Create a new workflow in the OTA Update GUI using the above script. Next, create a new deployment using the workflow to deploy an OTA update to a select group of edge linux devices. Monitor the deployment progress, by clicking the deployment tab and hitting the Refresh button. Select the Deployment to view the stdout and stderr logs.

REST API Usage:

If you plan to use the OTA update REST API to create a workflow using the above script, then the JSON data field "Command" in the REST API's HTTP request body must contain the following:

"echo `copy paste the contents of the my_script.py file here` > /usr/local/bin/my_script.py; python /usr/local/bin/my_script.py container_name container_image_name"

For example:

Using the Python Script

curl https://api.socketxp.com/v1/workflow \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-auth-token-goes-here>" \
  -d '{“Name”: "docker-container-update-workflow”, “Command": "echo `copy paste the contents of the my_script.py file here` > /usr/local/bin/my_script.py; python3.9 /usr/local/bin/my_script.py hello_world 123456789012.dkr.ecr.us-east-2.amazonaws.com/my-docker-registry/hello-world:latest"}'

Using the Shell Script

curl https://api.socketxp.com/v1/workflow \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-auth-token-goes-here>" \
  -d '{“Name”: "docker-container-update-workflow”, “Command": "echo `copy paste the contents of the my_script.sh file here` > /usr/local/bin/my_script.sh; sh /usr/local/bin/my_script.sh hello_world 123456789012.dkr.ecr.us-east-2.amazonaws.com/my-docker-registry/hello-world:latest"}'