Posted on

How to Automate building a docker container using Packer and Ansible from a templated image

Introduction

This guide will help you build a Docker container for the Moonshine-dev application, which is written in Haxe with a Gradle REST API. The container will consist of three layers:

  1. Base Layer: A Debian 12-based image prepared using Packer and Ansible, as referenced here: Building a Template Image using Packer and Ansible
  2. Intermediate Layer: Common provisioning steps to speed up future builds.
    moonshine-dev/base-latest
  3. Application Layer: Installation and configuration of the Moonshine-dev application.
    moonshine-dev/latest

See more here about Image Layers in Docker

Prerequisites

Ensure you have the following software and requirements met:

  • Super.Human.Installer (SHI) Instance: Use a SHI instance, which is a GUI wrapper around Vagrant. This instance is based on a STARTcloud template and already has Ansible installed.
  • Docker: Install Docker on the SHI instance. You can follow the official Docker installation guide for your operating system. or add the role startcloud.startcloud_roles.docker

Step 1: Clone the Repository

  1. Open a terminal in your SHI instance.
  2. Navigate to the directory where you want to clone the repository.
  3. Run the following command to clone the repository with submodules:
    git clone --recursive https://github.com/STARTcloud/vagrant_box_template_creator
  1. Change into the cloned repository directory:
    cd vagrant_box_template_creator

Step 2: Build the Base Layer

The base layer is a Debian 12-based image that serves as a foundation for other applications. It is built using Packer and Ansible.

Base Playbook

The base playbook for all STARTcloud images is as follows, this was referenced in the other article: Building a Template image with Packer and Ansible:

---
- name: "This Playbook Creates the Base Template via Ansible-Local"
  become: true
  gather_facts: true
  hosts: all
  collections:
    - startcloud.startcloud_roles
  roles:
    - role: startcloud.startcloud_roles.dependencies
    - role: startcloud.startcloud_roles.serial
    - role: startcloud.startcloud_roles.cockpit
    - role: startcloud.startcloud_roles.nfs
      vars:
        nfs_exports: []
        nfs_rpcbind_enabled: true
        nfs_rpcbind_state: started
    - role: startcloud.startcloud_roles.ntp
      vars:
        ntp_area: ""
        ntp_cron_handler_enabled: false
        ntp_enabled: true
        ntp_manage_config: false
        ntp_restrict:
          - "127.0.0.1"
          - "::1"
        ntp_servers:
          - "ntp1.prominic.net iburst"
          - "ntp2.prominic.net iburst"
        ntp_timezone: America/Chicago
        ntp_tinker_panic: false
    - role: startcloud.startcloud_roles.motd
      vars:
        add_footer: false
        add_update: true
        remove_default_config: true
        restore_default_config: false
        sysadmins_email: [email protected]
        sysadmins_signature: "STARTCloud Contact Email"
    - role: startcloud.startcloud_roles.cleanup

Build the Base Image

Run the following command to build the base image:
packer build -var-file='definitions/cloud-credentials.json' -var-file='definitions/vendor.json' -var-file='definitions/templates/x64/debian12-server.json' tasks/build-ansible-local.json

This command uses the build-ansible-local.json file to create a VirtualBox VDI file, which is then converted to a Docker image and pushed to both Vagrant Cloud and Docker Hub.

Step 3: Build the Intermediate Layer

The intermediate layer includes common provisioning steps to speed up future builds. It is built using the same Packer script (deploy.json) as the application layer.

Intermediate Layer Playbook

The playbook for the intermediate layer is as follows:

---
- name: "Setup Intermediate Layer"
  become: true
  gather_facts: true
  hosts: all
  roles:
    - name: startcloud.startcloud_roles.setup
    - name: startcloud.startcloud_roles.hostname
    - name: startcloud.startcloud_roles.dependencies
    - name: startcloud.startcloud_roles.service_user
    - name: startcloud.startcloud_roles.sdkman_install
    - name: startcloud.startcloud_roles.sdkman_java
    - name: startcloud.startcloud_roles.sdkman_gradle
    - name: startcloud.startcloud_roles.ssl
    - name: startcloud.startcloud_roles.supervisord

Build the Intermediate Layer

Run the following command to build the intermediate layer:

VERSION=0.0.1 sudo -E packer build -on-error=abort \
  -var-file='definitions/cloud-credentials.json' \
  -var-file='definitions/vendor.json' \
  -var-file='definitions/templates/x64/debian12-server.json' \
  -var "docker_hub_template_repo_name=debian12-server" \
  -var "docker_hub_template_repo_tag=0.0.8" \
  -var "playbook_file=provisioners/ansible/ansible_collections/moonshine/moonshine_roles/playbooks/main-base-playbook.yml" \
  -var "repo_version=base-latest" \
  -var 'repo_name=moonshine-dev' \
  providers/docker/deploy.json

Step 4: Build the Application Layer

The application layer installs and configures the Moonshine-dev application. It uses the intermediate layer as its base.

Application Layer Playbook

The playbook for the application layer is as follows:

---
-
  name: "Generating Playbook"
  become: true
  gather_facts: true
  hosts: all
  vars:
    core_provisioner_version: 0.0.1
    provisioner_name: PackerImageBuilder
    provisioner_version: 0.0.1
    settings:
      hostname: app
      domain: moonshine.dev
      server_id: 500001
      vagrant_user_pass: 'XaVuzq2vRV4fTk'
    debug_all: true
    selfsigned_enabled: true
    haproxy_ssl_redirect: true
    letsencrypt_enabled: false
    service_user: java_user
    service_group: java_group
    service_home_dir: /local/notesjava
    cert_dir: /secure
    installer_dir: /vagrant/installers
    completed_dir: /vagrant/completed
    domino_organization: STARTcloud
    domino_install_dir: /opt/hcl/domino/notes/latest/linux

  collections:
    - startcloud.startcloud_roles
    - startcloud.hcl_roles
    - moonshine.moonshine_roles

  roles:
    - name: startcloud.hcl_roles.domino_vagrant_rest_api
    - name: startcloud.startcloud_roles.haxe
    - name: moonshine.moonshine_roles.moonshinedev_deploy
    - name: startcloud.startcloud_roles.haproxy
      vars:
        haproxy_cfg: /vagrant/ansible/ansible_collections/moonshine/moonshine_roles/roles/moonshinedev_deploy/templates/moonshinedev-haproxy.cfg.j2

Build the Application Layer

Run the following command to build the application layer, note that the repo_name and repo_version would be based off your intermediary image/layer above:

VERSION=0.0.1 sudo -E packer build -on-error=abort \
  -var-file='definitions/cloud-credentials.json' \
  -var-file='definitions/vendor.json' \
  -var-file='definitions/templates/x64/debian12-server.json' \
  -var "docker_hub_template_repo_name=moonshine-dev" \
  -var "docker_hub_template_repo_tag=base-latest" \
  -var "playbook_file=provisioners/ansible/ansible_collections/moonshine/moonshine_roles/playbooks/main-application-playbook.yml" \
  -var "repo_version=latest" \
  -var 'repo_name=moonshine-dev' \
  providers/docker/deploy.json

Step 5: Testing the Image

After building the Docker image, you can test it by running the following command:

docker run -p 80:80/tcp -p 443:443/tcp -p 8080:8080/tcp -i -t startcloud/moonshinedev:latest /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf

or

docker run -p 80:80/tcp -p 443:443/tcp -p 8080:8080/tcp -i -t startcloud/moonshinedev:latest

This command will start the Moonshine-dev application in a Docker container, allowing you to test its functionality.

If you need to debug the docker container, you can access it with the following commands
1. list the docker container and grab its name or id:

sudo docker container ls

2. The you can use exec to access the container:

sudo docker container exec -it <CONTAINER_ID_OR_NAME> /bin/bash

Conclusion

By following this guide, you have successfully built a multi-layer Docker container for the Moonshine-dev application. Each layer serves a specific purpose, from providing a base Debian 12 image to installing and configuring the application itself. This modular approach allows for efficient and flexible image management, making it easier to update and maintain the application over time.