Automate custom docker image deployment on AWS
I will give it a try and explain all required steps with examples.

Please add comments with questions and improvement suggestions if anything is not well enough explained.

The OP (original poster) refers to awscli; while I provide corresponding examples, I also discuss limitations of this approach and give examples of doing same with Python.

EC2 deployment

The purpose of this answer is to demonstrate that as OP correctly assumed, no Web UI access neither human interaction is mandatory to deploy a container on an AWS EC2 instance.

Scope and limitations

In this use case, we want to start just one container manually for demo/research purposes, therefore we explicitly decide to do no orchestration. 

For a complex and possibly more realistic deployment scenario, this example is therefore not reusable. For same simplicity reasons, we will use EC2 service and create there a single virtual machine.

According to a short documentation research, there seems to be no official AMI with pre-installed Docker daemon. Thus we need to deploy some default EC2 instance and install there Docker daemon, then start the daemon. 

While using a private registry, a login there is required; for simplicity purposes, I give also an example to run a container from the public Docker Hub registry.

Also, I am not going to provide a copy-paste automation script but single steps which can be easily adopted towards individual needs. In any case, intermediate results require parsing and passing operations which is then a pure programming activity concern.

A boilerplate automation could be done either with bash using awscli or with Python, using the boto3 SDK for AWS.

Python is worth to consider because you can do better processing of JSON responses you get from AWS. You can also tell Python to execute SSH login and bash commands on the remote host.

Execution plan


A. Identify recent Linux AMI (Amazon Machine Image) ID
B. Create keypair and instance (further, also referenced as VM)
C. Connect to the instance
D. Install and configure Docker daemon
E. Run container


A. Identify recent Linux AMI image ID

Search for a recent and up-to-date Amazon Linux image following the official documentation:

$ aws ec2 describe-images --owners amazon --filters 'Name=name,Values=amzn2-ami-hvm-2.0.????????.?-x86_64-gp2' 'Name=state,Values=available' --query 'reverse(sort_by(Images, &amp;CreationDate))[:1].ImageId' --output text
ami-01f14919ba412de34


B. Create keypair and VM

With awscli, you would just say:

$ aws ec2 create-key-pair --key-name ec2-docker-test


However, the API sends you your public key wrapped in a JSON data message. Therefore, using Python it is way easier to process this data message to a .pem file:

import boto3
ec2 = boto3.resource('ec2')

# create a file to store the key locally
outfile = open('ec2-keypair.pem','w')

# call the boto ec2 function to create a key pair
key_pair = ec2.create_key_pair(KeyName='ec2-docker-test')

# capture the key and store it in a file
KeyPairOut = str(key_pair.key_material)
print(KeyPairOut)
outfile.write(KeyPairOut)


With the AMI ID we have got previously we can now create the VM:

instances = self.ec2r.create_instances(
                 ImageId=AMI_ID,  
                 MinCount=1,
                 MaxCount=1,
                 InstanceType=instance_type,
                 KeyName='ec2-docker-test'
             )


AWS API allows also lookup of instance state and IP. With little bit more  Python/boto3 stanzas you can acquire this data to know when you can proceed.

Note: Firewall consideration

To configure firewall access, please create a security group and link it to machines's VPC. For readability reasons, example is not included.

C. Test SSH connection

As soon as the instance is up and running, you can do everything you like by means of remote command execution.

In a Bash script, you would use ssh only or scp to upload a local bash script and run it remotely.

 ssh -t &lt;REMOTE_IP&gt; -i &lt;KEY_FILE&gt; &lt;COMMAND&gt;


With Python, you use further modules like paramiko to establish SSH connection and run commands remotely. 

client = paramiko.SSHClient()
client.connect(ip, username=username, key_filename=key_filename, port=port,
                           timeout=timeout,
                           auth_timeout=timeout)

stdin, stdout, stderr = ssh.exec_command("whoami")


For readability purposes, I list further commands unwrapped. In your script, you might want to use elementary helper subroutines to have calls like ssh_call(command).

D. Install and configure Docker daemon

As said above, anything else from here is running remote commands. 

That is, the concluding steps are, following an example in the official documentation; proceeding is same as you would do it locally, but you need to wrap it in your script and pass over intermediate results if required:


install and configure Docker daemon 
login to your Docker registry if needed
pull image
run Docker container


    sudo yum update -y
    sudo amazon-linux-extras install docker
    sudo service docker start
    sudo usermod -a -G docker ec2-user
    # relogin or continue with sudo, which you shouldn't
    aws ecr get-login --no-include-email --region region




E. Run container

Pull custom image and run container from a private registry in AWS.

    docker pull aws_account_id.REGISTRY.ecr.REGION.amazonaws.com/REPOSITORY/IMAGE:TAG
    docker run --name MYSERVICENAME -d -p PORT_HOST:PORT_CONTAINER IMAGE:TAG



Pull an official public  image and run another container listening on port 8001 in a single step:

    docker run --name nginx -d -p 8001:80 nginx:latest





Alternative scenario with ECS

As described in these Python/boto3 examples here and here, it's possible to define an machine cluster with the AWS ECS service and run containers there.

Please note that ECS cluster offers you an abstraction either from AWS EC2 or AWS Fargate to deliver the actual virtual machines.

If we go for EC2, again we need to pick up a recent machine image, but this we need a machine optimized for ECS. In a nutshell, these machines will have an ECS agent; through its configuration, we assign a machine to our target cluster. 

A small bonus is also that ECS machines have Docker daemon already preinstalled.

So, we create an ECS cluster and add a virtual machine to it.

ecs_client.create_cluster(clusterName=cluster_name)

ec2_client.run_instances(
        ImageId=AMI_ID,
        MinCount=1,
        MaxCount=1,
        InstanceType="t2.micro",
        IamInstanceProfile={
            "Name": "ecsInstanceRole"
        },
        UserData="#!/bin/bash \n echo ECS_CLUSTER=" + cluster_name + " &gt;&gt; /etc/ecs/ecs.config"
    )



In the cluster, we can create a task definition of the service which are both abstract of the container concept.

ecs_client.register_task_definition(
        containerDefinitions=[
        {
          "name": "&lt;MY_SERVICE&gt;",
          "image": "&lt;MY_IMAGE&gt;",
          "portMappings": [
            {
              "containerPort": 80,
              "hostPort": 80
            }
          ]
        }
        ],
        family="hello_world"
    )


Now, possibly you ask yourself, how would the Docker daemon on the instance know about your private registry to pull the specified image? For that, the machine's ECS agent needs additional configuration.

Now, you can launch the service, which will use the previously defined task to tell the previously authorized Docker daemon to pull the previously built and published image from ECR and run the container on the machine you've included in the ECS cluster in the data center house that Jeff built.

ecs_client.create_service(
        cluster=cluster_name,
        serviceName=service_name,
        taskDefinition=task_name,
        desiredCount=1,
        clientToken='request_identifier_string',
        deploymentConfiguration={
            'maximumPercent': 200,
            'minimumHealthyPercent': 50
        }
    )


Configuration of security groups in the ECS context has been highlighted on StackOverlow already. In short, these are further calls to the API parts related to security group management.

Conclusion

Both Bash and Python implementation options have their benefits and drawbacks.

Seen on long term, Python being a general purpose programming language with large community, allows complex string manipulation and abstractions from a cloud API or a system process.

Without knowing the real goal of the deployment, an end-to end of above steps might not be worth the effort in terms that for deploying a set of related containers which is more typical scenario, other approaches could be more pragmatic. 

Before stepping into technical implementation, best is to make well elaborated decisions and choices addressing business goals and budget:


how many times will automation need to run and in which time period vs. time to implement it end-to-end from the beginning?
assess whether you need only EC2, or ECS with EC2 either Fargate for stateless tasks, or maybe you need Kubernetes?


Further reading: "The Benefits of Managed Kubernetes vs. Amazon ECS" (short argument from a cloud consultant blog as of August 2019).


  A 2018 CNCF survey cites 83% of organizations use Kubernetes as
  their container orchestration solution vs. 24% for ECS.