The reliable pattern is simple: let Test Kitchen create an isolated machine, let Ansible converge the deployment, and let Chef InSpec verify the machine’s observable state through the kitchen-inspec verifier. Run the stages independently while developing, then use Kitchen’s full create → converge → setup → verify → destroy lifecycle in CI.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
This approach turns an Ansible role’s assumptions into executable checks for packages, services, ports, files, users, permissions, configuration values, and application responses—without treating a successful playbook run as proof that the deployment works.
As an Amazon Associate I earn from qualifying purchases.
What each tool contributes
Ansible applies the change
Ansible remains the system under test. Your playbook or role installs packages, writes configuration, creates service accounts, enables services, opens the intended listeners, and performs any application setup. Its output tells you what tasks changed, skipped, or failed, but it does not independently prove that the resulting machine satisfies the deployment contract.
Test Kitchen supplies repeatable targets
Test Kitchen is a harness for executing infrastructure code on one or more platforms in isolation. A Kitchen instance is the combination of a suite and a platform, such as the default-ubuntu instance produced by a suite named default and a platform named ubuntu. Drivers create the target, provisioners run infrastructure code, and suites define what is being tested.
#1 Best Overall
kitchen-inspec connects verification to Kitchen
The kitchen-inspec verifier runs Chef InSpec controls against the instance that Kitchen created. Set verifier: name: inspec in kitchen.yml. The verifier can use Kitchen’s host and port information by default, or you can provide explicit connection settings when the target requires them.
InSpec checks the resulting machine
Chef InSpec expresses compliance, security, and policy expectations in a human- and machine-readable language. It can inspect a local machine or connect to a remote host over SSH. Its resource model lets a control describe what must be true without coupling every assertion to the exact Ansible task that produced it.
Start with a deployment contract
Before writing Kitchen configuration, list the outcomes that operators and users depend on. These are the assertions that should survive a refactor of the role.
- The required package is installed, and its version is within the supported range.
- The service is enabled and running after convergence.
- Only intended ports are listening, with TLS enabled where the deployment requires it.
- Configuration files contain required values and have the correct owner and permissions.
- The service account exists with the expected shell, groups, home directory, and ownership.
- An application endpoint returns the expected status or response header.
- A second convergence is idempotent, meaning it does not make unnecessary changes.
The first six items are final-state checks. Idempotence is a behavior check and should be tested separately rather than inferred from a clean InSpec run.
Build the Kitchen project
Choose a driver and target platforms
The driver determines how an isolated target is created. A container driver can be fast for package and service checks; a virtual-machine or cloud driver can provide stronger operating-system fidelity. Select platforms that represent the systems your role actually supports, and add more than one platform when package names, service managers, paths, or defaults differ.
Define the provisioner, verifier, and suite
A minimal project has a Kitchen configuration, an Ansible convergence playbook, and an InSpec test directory. The following shape uses a Docker driver, an Ubuntu platform, the Ansible provisioner, and kitchen-inspec:
provisioner:
name: ansible
driver:
name: docker
platforms:
- name: ubuntu
verifier:
name: inspec
suites:
- name: default
provisioner:
playbook: converge.yml
verifier:
inspec_tests:
- test/integration/default
The exact driver and Ansible provisioner options depend on the Kitchen plugins and target environment. Pin compatible plugin versions in the project and validate those versions in continuous integration; a configuration that works only because of locally installed, floating dependencies is not repeatable.
Rank #2
Use explicit variables and inventory
Keep variables, inventory, and environment-specific assumptions visible in the suite configuration or files checked into the project. Explicit inputs make it easier to tell whether a failure came from the playbook, the target image, or the verifier. Avoid silently depending on a developer’s workstation credentials or globally installed Ansible configuration.
Organize InSpec controls where Kitchen can find them
kitchen-inspec’s default convention is test/integration/%suite%. For the default suite, that resolves to test/integration/default. You can override the location with inspec_tests.
A suite can point to several kinds of profile source:
| Source | When it fits | Trade-off |
|---|---|---|
| Local directory | Controls are specific to the role and reviewed with its code. | Assertions are easy to change with the deployment but are less reusable. |
| Git repository | Several repositories need a shared profile with normal version review. | CI must fetch the requested revision and remain available to the build. |
| Supermarket profile | A centrally published profile is appropriate for the organization. | Execution depends on profile availability and version selection. |
| Compliance profile | A team already maintains controls as a compliance profile. | Profile ownership and update cadence need to be coordinated with the deployment. |
Local controls are usually the clearest starting point. Move stable, cross-project policy checks into a shared profile only when the reuse and independent versioning justify the additional dependency.
Write controls around observable behavior
Packages
Check that the package exists, and check a version only when the deployed version is part of the contract. A version assertion that merely mirrors an implementation detail can create unnecessary maintenance.
describe package('nginx') do
it { should be_installed }
end
If a particular release is required, express that requirement explicitly using the version matcher supported by your InSpec version, and document whether the requirement is an exact version or an acceptable range.
Services
describe service('nginx') do
it { should be_enabled }
it { should be_running }
end
This catches a common gap: a playbook can start a service during convergence without enabling it for the next reboot, or it can enable a unit that is currently unhealthy.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #3
Ports and transport
describe port(443) do
it { should be_listening }
end
For a deployment that requires encrypted transport, combine a listener check with an application-level request that validates the expected HTTPS behavior. Also consider a negative check for an unintended clear-text listener when that is part of the security contract. Do not assert a port merely because the example role happens to use it; assert only ports that the supported deployment promises.
Files and permissions
describe file('/etc/nginx/nginx.conf') do
it { should exist }
its('mode') { should cmp '0644' }
its('owner') { should eq 'root' }
its('group') { should eq 'root' }
its('content') { should match /worker_processes/ }
end
Use content checks for values that matter, such as a required upstream, bind address, or security setting. Keep the assertion focused enough that a harmless formatting change does not fail the deployment.
Users and service accounts
describe user('app') do
it { should exist }
its('shell') { should eq '/usr/sbin/nologin' }
its('groups') { should include 'app' }
end
Check ownership on application directories separately. A service account can exist with the right shell while files remain writable by an unintended group.
Application responses
describe http('http://127.0.0.1/health') do
its('status') { should cmp 200 }
end
An endpoint check verifies that the service is not merely running but can answer a request. Use a local address when the test is intended to isolate the machine, and add externally meaningful checks in a separate integration layer when DNS, load balancers, certificates, or dependencies are outside the Kitchen instance.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Run the lifecycle deliberately
During development, separate stages make diagnosis faster. In CI, the complete lifecycle gives every run a clean target and predictable cleanup.
- Create: Run
kitchen create default-ubuntuto create the isolated instance. Usekitchen listfirst if you need the exact generated instance name. - Converge: Run
kitchen converge default-ubuntuto execute the Ansible playbook. Treat an Ansible task failure as a provisioning failure, not an InSpec failure. - Setup: Kitchen performs the setup stage needed by the verifier and transport. If setup fails, inspect connectivity, authentication, the target image, and verifier connection settings before changing controls.
- Verify: Run
kitchen verify default-ubuntu. kitchen-inspec connects to the Kitchen host and evaluates the controls in the configured profile. - Destroy: Run
kitchen destroy default-ubuntuwhen you are finished, especially after debugging a failure. - Test end to end: Run
kitchen test default-ubuntuto execute the documented create → converge → setup → verify → destroy flow.
Use the instance-specific form while iterating, then run the full suite and platform matrix before merging. If you need to inspect a failed machine, preserve it temporarily for diagnosis and destroy it once the investigation is complete.
Separate provisioning failures from verification failures
| Symptom | Likely layer | First checks |
|---|---|---|
| Kitchen cannot create the instance | Driver or platform | Driver installation, image availability, container or VM permissions, and platform name. |
| Ansible cannot connect | Provisioner transport | Inventory, SSH or connection settings, credentials, Python availability, and target readiness. |
| Ansible task fails | Playbook or target assumptions | Task output, variables, package repositories, service names, paths, and operating-system differences. |
| Setup cannot reach the host | Kitchen transport or verifier setup | Kitchen’s host and port, explicit verifier connection options, firewall rules, and authentication. |
| InSpec reports a missing package or service | Deployment contract or control | Whether convergence completed, the target’s actual package and unit names, and the control’s platform assumptions. |
| An HTTP control returns the wrong status | Application configuration or readiness | Service logs, bind address, listening port, TLS requirements, and whether the endpoint needs startup time or dependencies. |
Do not “fix” a failing control by weakening it until you have confirmed that the requirement itself is wrong. A failed assertion often exposes an undocumented platform difference or an incomplete deployment.
Rank #4
Test idempotence as a separate property
Final-state controls can all pass even when a role changes files or restarts services on every run. To check idempotence, converge the same instance again and inspect Ansible’s second-run output for unexpected changes. Pay particular attention to templated files, generated secrets, archive extraction, service handlers, and commands that lack a meaningful change condition.
Free tools Windows power users keep installed
One-click scans. No signup required.
Keep this check distinct from InSpec’s state assertions. InSpec answers whether the machine is in the required state; the repeated-convergence check answers whether the role reaches that state without continuing to mutate it.
Design a useful platform matrix
Platform coverage should reflect supported deployments rather than every image that a driver can launch. For each additional platform, decide whether it adds meaningful confidence.
| Dimension | Question to answer |
|---|---|
| Isolation | Does the driver prevent host state from masking failures? |
| Operating-system fidelity | Does the image use the same package manager, service manager, paths, and defaults as production? |
| Provisioning time | Can the target start quickly enough for the frequency of CI runs? |
| Transport and authentication | Does the test exercise the SSH or connection behavior used in deployment? |
| CI availability | Can the build environment create the target reliably with the required permissions? |
| Parallelism | Can multiple platform and suite combinations run without exhausting CI capacity? |
| Cleanup cost | Will failed jobs leave resources that require manual intervention? |
| Production parity | Are the image, kernel expectations, and base packages close enough to reveal real defects? |
Use separate suites when the deployment contract genuinely differs. Use one suite with multiple platforms when the same observable requirements should hold everywhere.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Make CI results useful
Run the matrix on every change that affects the role, its defaults, its dependencies, or its InSpec controls. Retain the Kitchen and Ansible logs so a failed verification can be traced back to the convergence that produced the machine.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallWhen auditability matters, retain machine-readable InSpec output as a CI artifact. The InSpec command set supports formats including JSON; for a profile run outside Kitchen, the form is:
inspec exec test/integration/default --reporter json
Use the verifier’s supported reporting configuration when the run is launched through Kitchen, and confirm the resulting file is uploaded by the CI job. Keep human-readable output as well; JSON is useful for systems and retention, while console output is faster for a developer diagnosing a failed control.
Best Value
Improve the controls without overfitting them
- Assert the package or service that users depend on, not an unrelated implementation artifact.
- Check security-sensitive permissions and ownership explicitly.
- Prefer a meaningful configuration value or endpoint response over matching an entire generated file.
- Use platform conditionals only when the contract truly differs; excessive conditionals can hide an unsupported platform.
- Give controls names that describe the requirement, so a CI failure explains what broke.
- Keep external dependency checks separate from host checks when the dependency is not created by Kitchen.
- Version shared profiles and review profile changes with the same care as playbook changes.
A practical project layout
A small repository can remain easy to navigate with a layout like this:
.
├── .kitchen.yml
├── converge.yml
├── roles/
│ └── application/
└── test/
└── integration/
└── default/
├── controls/
│ └── deployment.rb
└── inspec.yml
The exact role layout is up to your Ansible project. The important boundary is that converge.yml expresses how the target is built and the InSpec controls express what must be true afterward.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesCommon design mistakes
Testing only whether Ansible finished
A playbook can complete while a service is disabled, a configuration file is world-writable, or an endpoint is unreachable. Add state and behavior controls for the outcomes that matter.
Using a target unlike production
A fast container may not expose systemd behavior, kernel features, storage layout, or network boundaries that production relies on. Keep the fast target for broad feedback, and add a more faithful driver or image for assumptions the container cannot exercise.
Putting every assertion in one control file
Group controls by contract area—packages, service, configuration, access, and endpoint behavior—so failures are scannable and profile reuse remains possible.
Leaving instances behind
Unmanaged instances create stale state and consume CI resources. Use kitchen destroy after debugging and the complete kitchen test lifecycle in automated runs.
Recommended Free Tools
Quick Recap
Definition of done
- A clean Kitchen instance can be created with pinned, compatible plugins and a declared platform.
- Ansible converges with explicit inventory and variables.
- InSpec verifies packages, services, listeners, files, users, permissions, and application behavior required by the contract.
- A second convergence is checked for unnecessary changes.
- Supported platforms are exercised according to production differences and CI capacity.
- Failures identify whether creation, transport, convergence, setup, or verification broke.
- Instances are destroyed and machine-readable results are retained when auditability requires them.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

