Goss: Fast, YAML-Based Server Validation for Infrastructure Testing

Goss: Fast, YAML-Based Server Validation for Infrastructure Testing

Goss is a Go-based server validation tool that runs tests in milliseconds. Where Serverspec and InSpec connect via SSH and execute Ruby, Goss runs as a single binary directly on the target system. Tests are defined in YAML, results are immediate.

The key differentiator: goss validate runs your entire spec in parallel. A 50-check spec that takes 2 minutes in Serverspec (sequential SSH calls) takes under a second in Goss.

Installing Goss

# Linux x86_64
curl -L https://github.com/goss-org/goss/releases/latest/download/goss-linux-amd64 \
  -o /usr/local/bin/goss
chmod +x /usr/local/bin/goss

# macOS (for dgoss/Docker testing)
brew install goss

# Verify
goss --version

For CI, pin a specific version:

GOSS_VERSION=v0.4.4
curl -L "https://github.com/goss-org/goss/releases/download/${GOSS_VERSION}/goss-linux-amd64" \
  -o /usr/local/bin/goss

Writing a goss.yaml

The spec file (goss.yaml) is straightforward YAML:

# goss.yaml
package:
  nginx:
    installed: true
    versions:
      - "1.24.*"
  curl:
    installed: true
  openssl:
    installed: true

service:
  nginx:
    enabled: true
    running: true
  sshd:
    enabled: true
    running: true

port:
  tcp:80:
    listening: true
    ip:
      - "0.0.0.0"
  tcp:443:
    listening: true
  tcp6:22:
    listening: false  # SSH should not listen on IPv6

file:
  /etc/nginx/nginx.conf:
    exists: true
    mode: "0644"
    owner: root
    group: root
    contains:
      - "worker_processes auto"
      - "gzip on"
  /var/log/nginx:
    exists: true
    filetype: directory
    owner: nginx

user:
  nginx:
    exists: true
    uid: 101
    gid: 101
    home: /var/cache/nginx
    shell: /usr/sbin/nologin
    groups:
      - nginx

group:
  nginx:
    exists: true
    gid: 101

process:
  nginx:
    running: true

command:
  "nginx -t":
    exit-status: 0
    stderr:
      - "syntax is ok"
      - "test is successful"
  "systemctl is-active nginx":
    exit-status: 0
    stdout:
      - active

http:
  http://localhost:
    status: 200
    timeout: 5000
    body:
      - "Welcome to nginx"
  http://localhost/healthz:
    status: 200
    allow-insecure: false

Run it:

goss validate
# or
goss validate --format documentation
# or with JUnit output for CI
goss validate --format junit > test-results.xml

Auto-Adding Resources

Instead of writing YAML by hand, autogenerate specs from the current system state:

# Add a package check
goss add package nginx

# Add a service check
goss add service nginx

# Add a port check
goss add port 80

# Add a file check
goss add file /etc/nginx/nginx.conf

# Add a command check
goss add command "nginx -v"

# Add everything for a process
goss autoadd nginx

goss autoadd nginx detects the nginx process and automatically adds: the package, service, user, group, and process checks. This is the fastest way to capture current state as a spec.

After autopiloting, review goss.yaml and tighten assertions that are too permissive (like removing specific PIDs and keeping only process running checks).

Output Formats

# Human-readable (default)
goss validate

# Tap format (for tap-compliant tools)
goss validate --format tap

# JSON
goss validate --format json

# JUnit XML (for CI test reporting)
goss validate --format junit --output-file results.xml

# Documentation (verbose)
goss validate --format documentation

# Nagios-compatible exit codes
goss validate --format nagios

gossfile: Composable Specs

Break large specs into files by role:

# goss.yaml (main entry point)
gossfile:
  ./specs/base.yaml: {}
  ./specs/webserver.yaml: {}
  ./specs/tls.yaml: {}

# Override or extend
package:
  custom-package:
    installed: true
# specs/base.yaml
package:
  curl:
    installed: true
  vim:
    installed: true
  ca-certificates:
    installed: true

service:
  sshd:
    enabled: true
    running: true

kernel-param:
  vm.swappiness:
    value: "10"
  net.ipv4.ip_forward:
    value: "0"
# specs/webserver.yaml
package:
  nginx:
    installed: true

service:
  nginx:
    enabled: true
    running: true

port:
  tcp:80:
    listening: true
  tcp:443:
    listening: true

http:
  http://localhost:
    status: 200

Now different server roles compose specs:

# goss-database.yaml
gossfile:
  ./specs/base.yaml: {}
  ./specs/postgresql.yaml: {}
# goss-webserver.yaml
gossfile:
  ./specs/base.yaml: {}
  ./specs/webserver.yaml: {}
  ./specs/tls.yaml: {}
goss -g goss-webserver.yaml validate
goss -g goss-database.yaml validate

Variables and Templates

Goss supports Go templates with a variables file:

# vars.yaml
port: 8080
version: "1.24"
log_dir: /var/log/nginx
# goss.yaml (with templates)
package:
  nginx:
    installed: true
    versions:
      - "{{ .Vars.version }}.*"

port:
  tcp:{{ .Vars.port }}:
    listening: true

file:
  {{ .Vars.log_dir }}:
    exists: true
    filetype: directory
goss --vars vars.yaml validate
# or with inline vars
goss --vars-inline '{"port": 9090}' validate

Different environments use different vars files:

# Production
goss --vars vars/prod.yaml validate

# Staging
goss --vars vars/staging.yaml validate

dgoss: Testing Docker Images

dgoss is a wrapper that starts a Docker container, copies in goss.yaml, runs goss validate inside it, then removes the container:

# Install dgoss
curl -L https://github.com/goss-org/goss/releases/latest/download/dgoss \
  -o /usr/local/bin/dgoss
chmod +x /usr/local/bin/dgoss

Test a Docker image:

# Run goss inside the container
dgoss run nginx:1.24 -p 80:80

# Edit mode — start container and generate goss.yaml interactively
dgoss edit nginx:1.24

# With environment variables
dgoss run -e NGINX_PORT=8080 myapp:latest

Write your spec:

# goss.yaml for Docker nginx
package:
  nginx:
    installed: true

service:
  nginx:
    enabled: true
    running: true

port:
  tcp:80:
    listening: true

http:
  http://localhost:
    status: 200
    timeout: 3000

Run in CI to validate your Docker image before pushing:

# GitHub Actions
- name: Validate Docker image
  run: |
    docker build -t myapp:test .
    dgoss run myapp:test

cgoss: Cloud Init Testing

cgoss extends dgoss patterns for cloud-init testing. For VMs, the common pattern is:

  1. Packer builds an image and runs Goss as a provisioner
  2. Goss validates the image before it's published
{
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "curl -L https://github.com/goss-org/goss/releases/latest/download/goss-linux-amd64 -o /usr/local/bin/goss",
        "chmod +x /usr/local/bin/goss"
      ]
    },
    {
      "type": "file",
      "source": "goss.yaml",
      "destination": "/tmp/goss.yaml"
    },
    {
      "type": "shell",
      "inline": [
        "cd /tmp && goss validate --format documentation"
      ]
    }
  ]
}

goss serve: Expose as Health Check Endpoint

Goss can run as an HTTP server that exposes your spec as a health endpoint:

# Start health endpoint on port 8080
goss serve --listen-addr :8080 --format json

# Or as a daemon
goss serve --listen-addr :8080 &

# Check from anywhere
curl http://server:8080/healthz

Response:

{
  "results": [...],
  "summary": {
    "failed-count": 0,
    "test-count": 12
  },
  "summary-line": "Count: 12, Failed: 0, Duration: 0.042s"
}

This makes Goss work as a Kubernetes readiness/liveness probe:

# Kubernetes deployment
readinessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 15
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 30

Start goss serve as a sidecar or in the app container's init process.

CI Integration

GitHub Actions

name: Goss Infrastructure Tests

on:
  push:
    branches: [main]
  pull_request:

jobs:
  goss:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Goss
        run: |
          curl -L https://github.com/goss-org/goss/releases/download/v0.4.4/goss-linux-amd64 \
            -o /usr/local/bin/goss
          chmod +x /usr/local/bin/goss

      - name: Install dgoss
        run: |
          curl -L https://github.com/goss-org/goss/releases/download/v0.4.4/dgoss \
            -o /usr/local/bin/dgoss
          chmod +x /usr/local/bin/dgoss

      - name: Build image
        run: docker build -t app:test .

      - name: Validate with Goss
        run: dgoss run app:test

      - name: Upload results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: goss-results
          path: goss-results.json

Integration with Ansible

Run Goss as the final step in an Ansible playbook:

# roles/nginx/tasks/main.yml
- name: Install nginx
  ansible.builtin.package:
    name: nginx
    state: present

- name: Start nginx
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: yes

- name: Copy Goss spec
  ansible.builtin.copy:
    src: goss.yaml
    dest: /tmp/goss.yaml

- name: Install Goss
  ansible.builtin.get_url:
    url: https://github.com/goss-org/goss/releases/latest/download/goss-linux-amd64
    dest: /usr/local/bin/goss
    mode: '0755'

- name: Run Goss validation
  ansible.builtin.command:
    cmd: goss -g /tmp/goss.yaml validate --format documentation
  register: goss_result
  changed_when: false

- name: Show results
  ansible.builtin.debug:
    msg: "{{ goss_result.stdout_lines }}"

Retry Logic for Eventually-Consistent Checks

For services that take time to start, use retry:

goss validate --retry-timeout 30s --sleep 1s

Or in goss.yaml for specific checks:

http:
  http://localhost:
    status: 200
    timeout: 5000

# Retry happens at the CLI level

Useful after reboots, slow service starts, or when testing cloud-init provisioning where the service takes 10-20 seconds to fully initialize.

Comparing Goss vs Serverspec vs InSpec

Feature Goss Serverspec InSpec
Speed Very fast (parallel) Slow (sequential SSH) Moderate
Language YAML Ruby/RSpec Ruby/RSpec
Remote testing Copy binary + SSH Native SSH Native SSH + plugins
Docker support dgoss wrapper Test Kitchen Test Kitchen
Compliance profiles No No Yes
Auto-generation goss autoadd No No
Binary deps Single binary Ruby + gems Ruby + gems
Readability High (YAML) High (RSpec DSL) High (RSpec DSL)

Use Goss when: you want maximum speed, minimal dependencies, or need a health check endpoint. Use Serverspec when: you're already in a Ruby/Test Kitchen ecosystem and want RSpec patterns. Use InSpec when: you need compliance profiles, waivers, or Chef Automate integration.

Summary

Goss combines three things most other tools separate: fast local validation, autogeneration from live state, and a built-in health endpoint. The YAML format is readable enough that non-developers can review and understand specs. For Docker image validation, dgoss is the fastest way to add confidence that your images are correctly configured before they hit production.

Start with goss autoadd on a configured server to capture baseline state, prune the generated spec to meaningful assertions, and integrate goss validate into every Ansible playbook run or Docker build. The cost is a single Go binary and a YAML file — the payoff is a continuously validated infrastructure spec.

Read more

Start now free