Skip to content

Sandbox – local testing

sandbox/ contains a Docker-based environment for testing playbooks against a throwaway container instead of a real server. Use it to validate changes before applying them to staging or production.

How it works

sandbox/init.sh does the following on every run:

  1. Generates a temporary ed25519 SSH key pair.
  2. Tears down and removes any existing container (and its local image).
  3. Builds and starts a fresh container with the temporary public key injected.
  4. Waits for SSH to be ready on 127.0.0.1:2222.
  5. Runs ansible-playbook against the container.

The container is a plain Ubuntu image with sshd and sudo configured. It mimics a fresh server.

Some roles are disabled in sandbox mode because they require external services or elevated system access that is not available in a container: - auditd_enabled: false - cadvisor_enabled: false - cloudflare_enabled: false - sqlbak_agent_enabled: false

First-time setup

The sandbox defaults to the local inventory, and inventories/local/ is gitignored on purpose - it is yours, not the repo's. Create it once from the tracked example:

mkdir -p inventories/local
cp sandbox/hosts.local.yml.example inventories/local/hosts.yml

The example puts the container in both web and db, so web.yml, db.yml and site.yml all have a host to run against. init.sh overrides the connection variables at run time, so you only need to edit the file if you run ansible-playbook against that inventory yourself.

Basic usage

Run the default playbook (site.yml) against the local container:

bash sandbox/init.sh

Examples

Run a specific playbook

bash sandbox/init.sh --playbook web.yml

Dry run (check mode)

init.sh rebuilds the container from scratch on every run, and a dry run against a bare host fails almost immediately: check mode only simulates the package installs, so the first task depending on one of them errors out (common_baseline : Set timezone fails because tzdata was never really installed). Converge the container first, then dry-run against the running container directly:

bash sandbox/init.sh --playbook web.yml
ansible-playbook -i inventories/local/hosts.yml web.yml --check --diff

Run a single role

bash sandbox/init.sh --role deployment

Use a real inventory (prod/stage) instead of local

Useful when you need real host variables (e.g. to test Cloudflare config):

bash sandbox/init.sh --inventory=prod --role=cloudflare --limit bkdk-web-01

Note: Ansible will still connect to the local container – the inventory is used only to load variables for the specified host.

Run a specific playbook against a real inventory host

bash sandbox/init.sh --inventory=prod web.yml --limit bkdk-web-01

Limitations

  • --role and --playbook cannot be combined (the script will error).
  • --role generates a one-task playbook in the repo root (.sandbox-role-<name>.yml) and removes it on exit. It has to live there: playbook_dir decides which group_vars/ Ansible loads and where roles resolve keys/*.pub from, so a playbook outside the repo silently loses group_vars/all/.
  • The container does not persist between runs – every init.sh call starts fresh. It does keep running afterwards, so you can point ansible-playbook at inventories/local/hosts.yml yourself for repeat or --check runs.
  • Roles that call external APIs (Cloudflare) will still make real API calls if not disabled.

Docker files

sandbox/
├── init.sh                        # Entry point
├── hosts.local.yml.example        # Copy to inventories/local/hosts.yml
└── docker/
    ├── Dockerfile                 # Ubuntu image with sshd + sudo
    └── docker-compose.yml         # Exposes port 2222 → 22

The SSH port is fixed at 2222 on the host. Make sure nothing else is bound to that port before running.