Deploy Your App in a Virtual Machine Using Docker: A Secure Development Sandbox

December 23, 2024

Introduction

Virtual machines (VMs) provide a sandboxed environment for running applications, while Docker streamlines deployment through containerization. Combining the two gives you hardware isolation, consistent environments, and easy scalability—whether you’re testing locally or deploying to a cloud VM. In this guide, we’ll deploy a Dockerized app inside a VM, covering both local setups (e.g., VirtualBox) and cloud platforms (AWS, Google Cloud, etc.).

Why Docker in a VM?

  • Isolation: Keep your host machine clean and avoid dependency conflicts.
  • Security: Containers run inside a VM’s sandbox, adding an extra layer of protection.
  • Portability: Migrate your entire VM + Docker setup between local and cloud environments.
  • Snapshot Testing: Use VM snapshots to test deployments risk-free.

Perfect for developers, homelabs, or production workloads requiring strict environment control.

Step 1: Set Up Your Virtual Machine

Option A: Local VM (VirtualBox/VMware)

  1. Download VirtualBox or VMware Workstation Player.

  2. Create a new VM:

    • OS: Ubuntu Server 22.04 LTS (lightweight, Docker-friendly).
    • Resources: Allocate 2+ CPU cores, 4GB RAM, 20GB disk space.
    • Networking: Use NAT (for internet access) and Port Forwarding (to expose ports later).
  3. Install the OS and enable SSH:

    sudo apt update && sudo apt install openssh-server -y
    sudo systemctl enable ssh --now

Option B: Cloud VM (AWS/Azure/Google Cloud)

  1. Create a VM instance:
    • Use Ubuntu 22.04 LTS.
    • Select a minimum tier (e.g., AWS t3.micro, Google Cloud e2-micro).
  2. Configure security groups:
    • Allow inbound SSH (port 22) and HTTP (port 80/443) traffic.
  3. Connect via SSH:
    ssh -i your-key.pem ubuntu@your-vm-public-ip

Step 2: Install Docker in the VM

Run these commands in your VM:

# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker  # Refresh group permissions

# Verify installation
docker run hello-world

Step 3: Containerize Your Application

  1. Create a Dockerfile for your app. Example for a Python app:

    FROM python:3.9-slim
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    COPY . .
    CMD ["python", "app.py"]
  2. Build and run with Docker Compose:

    # docker-compose.yml
    version: '3.8'
    services:
      web:
        build: .
        ports:
          - "8000:8000"
        environment:
          - DEBUG=0
        restart: unless-stopped

    Start the container:

    docker compose up -d

Step 4: Access Your Dockerized App

For Local VMs:

  1. Set up VirtualBox port forwarding:
    • Go to VM Settings > Network > Advanced > Port Forwarding.
    • Add a rule: Host IP=127.0.0.1, Host Port=8000, Guest Port=8000.
  2. Access via http://localhost:8000 on your host machine.

For Cloud VMs:

  1. Open the firewall port in your cloud provider’s dashboard.
  2. Access via http://<your-vm-public-ip>:8000.

Step 5: Security Best Practices

  1. Update the VM:

    sudo apt update && sudo apt upgrade -y
  2. Harden SSH Access:

    • Disable root login:
      sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
      sudo systemctl restart ssh
    • Use SSH keys instead of passwords.
  3. Secure Docker:

    • Avoid running containers as root:
      USER 1000:1000  # Add to your Dockerfile
    • Scan images for vulnerabilities:
      docker scan <your-image>
      
  4. Automate Backups:

    • For local VMs: Use VirtualBox snapshots.
    • For cloud VMs: Enable scheduled disk snapshots.

Troubleshooting

  • Docker Service Not Running:

    sudo systemctl status docker
    sudo journalctl -u docker.service  # Check logs
  • App Not Accessible?

    1. Check VM firewall rules.
    2. Verify port mappings in docker-compose.yml.
    3. Test internally:
      curl localhost:8000  # Inside the VM
  • Out of Disk Space:
    Clean unused Docker resources:

    docker system prune -a --volumes

When to Use VMs vs. Bare Metal Docker

  • Use a VM:
    • Testing untrusted code.
    • Isolating production workloads.
    • Running on cloud infrastructure.
  • Use Bare Metal Docker:
    • Resource-constrained environments.
    • CI/CD pipelines needing raw speed.

Conclusion

Deploying Docker inside a virtual machine gives you the best of both worlds: containerization efficiency and VM-level isolation. Whether you’re simulating a production environment locally or scaling in the cloud, this setup ensures consistency while keeping your host system clean and secure.

Next Steps: