Exploring Cloud Automation and DevOps with VirtualBox and Docker
Mohamad's interest is in Programming (Mobile, Web, Database and Machine Learning). He is studying at the Center For Artificial Intelligence Technology (CAIT), Universiti Kebangsaan Malaysia (UKM).
Introduction
Modern cloud administration relies heavily on automation, orchestration, and code-based deployments. Instead of manually creating and configuring servers one at a time, administrators define infrastructure and application settings using configuration files and deployment scripts. These files can then be reused to create consistent environments whenever needed.
In this hands-on activity, readers will use VirtualBox and Docker to build a simple application environment while exploring many of the techniques used in modern cloud operations.
Throughout this tutorial, readers will learn how to:
Deploy infrastructure using code
Work with YAML configuration files
Automate repetitive tasks
Manage multiple services together
Observe configuration drift
Create repeatable deployments
Understand deployment artifacts
Explore event-driven workflows
The lab environment is intentionally simple, requiring only a virtual machine and Docker containers, yet it demonstrates many of the same practices used in enterprise cloud platforms.
Learning Objectives
After completing this lab, readers should be able to:
Create and manage Docker containers
Deploy services using Docker Compose
Understand imperative and declarative deployment approaches
Work with YAML configuration files
Recognize configuration drift
Automate repetitive tasks
Manage multi-container deployments
Understand deployment artifacts
Relate practical Docker deployments to real-world cloud operations
Lab Environment
This tutorial assumes the following environment:
Host Operating System
- Windows 11
Virtualization Platform
- Oracle VirtualBox
Guest Operating System
- Xubuntu 20.04 LTS
Container Platform
- Docker Engine
Before proceeding, verify Docker is installed correctly.
Check Docker:
docker --version
Expected output:
Docker version xx.xx.xx
Check Docker Compose:
docker compose version
Expected output:
Docker Compose version xx.xx.xx
If both commands return version information, the environment is ready.
Activity 1 – Imperative Deployment
Objective
Deploy a container manually using Docker commands.
In traditional environments, administrators often provision infrastructure one step at a time. This approach provides complete control but requires more effort as environments grow larger.
Create a Docker network:
docker network create appnet
Create a web server container:
docker run -d \
--name web1 \
--network appnet \
-p 8080:80 \
nginx
Verify the container is running:
docker ps
Example output:
CONTAINER ID IMAGE STATUS
xxxxxxxxxxxx nginx Up xx seconds
Open Firefox inside the Xubuntu virtual machine and browse to:
http://localhost:8080
Readers should see the default Nginx welcome page.
Understanding What Happened
Several actions occurred during the deployment process:
A virtual network named appnet was created.
A Docker container named web1 was created.
The container was attached to the appnet network.
Port 8080 on the virtual machine was mapped to port 80 inside the container.
The Nginx web server started automatically.
The administrator explicitly instructed Docker to perform every step.
This deployment style offers flexibility and precise control, but it can become difficult to maintain when deploying dozens or hundreds of servers.
Activity 2 – Declarative Deployment
Objective
Deploy infrastructure using a configuration file.
Instead of manually entering deployment commands, modern environments often describe the desired state using configuration files.
Create a project directory:
mkdir cloud-automation-lab
cd cloud-automation-lab
Create a file named:
compose.yaml
Add the following content:
services:
web1:
image: nginx
ports:
- "8080:80"
Save the file.
Deploy the environment:
docker compose up -d
Verify deployment:
docker ps
You should again see an Nginx container running.
Open Firefox and browse to:
http://localhost:8080
The same Nginx welcome page should appear.
Understanding What Happened
Notice that readers did not manually create networks or containers.
Instead, they described the desired outcome:
services:
web1:
image: nginx
Docker Compose interpreted the configuration and automatically created the required resources.
This approach is often called a declarative deployment model because administrators describe the desired state rather than every individual action.
Benefits include:
Easier maintenance
Improved consistency
Better scalability
Simpler collaboration
Easier documentation
As environments become larger, declarative configurations become increasingly valuable.
Activity 3 – Exploring YAML
Docker Compose files use YAML (Yet Another Markup Language).
Examine the Compose file:
services:
web1:
image: nginx
ports:
- "8080:80"
Several important YAML characteristics can be observed.
Key-Value Structure
YAML stores information as keys and values.
Example:
image: nginx
The key is:
image
The value is:
nginx
Indentation Matters
YAML relies on indentation to organize information.
For example:
services:
web1:
image: nginx
The image setting belongs to the web1 service because of its indentation.
Incorrect indentation can cause deployment failures.
For example:
services:
web1:
image: nginx
This file is invalid and will generate errors.
Human Readability
One reason YAML is widely used is its readability.
Many administrators can understand a YAML file even without prior experience.
Compare:
image: nginx
with:
{
"image": "nginx"
}
Both contain the same information, but many people find YAML easier to read when files become larger.
Common YAML Use Cases
YAML is heavily used throughout cloud environments.
Examples include:
Docker Compose
Kubernetes
Ansible
AWS CloudFormation
GitHub Actions
CI/CD Pipelines
Learning YAML is therefore a valuable skill for cloud administrators and DevOps engineers.
Experimenting with YAML
Modify the Compose file:
services:
web1:
image: nginx
ports:
- "8081:80"
Save the file.
Apply the changes:
docker compose up -d
Browse to:
http://localhost:8081
The Nginx page should still appear.
This simple change demonstrates one of the biggest advantages of configuration files: environments can be modified by editing a few lines of text rather than manually reconfiguring systems.