The skaffold dev command, when configured for JSON output, actually emits events that represent stages of the development loop, not just a final state.

Let’s see this in action. Imagine you have a simple application with a Dockerfile and a skaffold.yaml:

apiVersion: skaffold/v2beta10
kind: Config
build:
  artifacts:
    - image: my-app
      docker:
        dockerfile: Dockerfile
deploy:
  kubectl: {}

And a basic Dockerfile:

FROM ubuntu
RUN echo "hello" > /app/hello.txt
CMD ["cat", "/app/hello.txt"]

When you run skaffold dev --output-format=json, you’ll see a stream of JSON objects. Here’s a simplified, illustrative example of what that might look like:

{"event": "build:artifact:pending", "id": "my-app", "timestamp": "2023-10-27T10:00:00Z"}
{"event": "build:artifact:complete", "id": "my-app", "timestamp": "2023-10-27T10:01:05Z", "imageName": "gcr.io/my-project/my-app:abcdef123456"}
{"event": "deploy:pending", "timestamp": "2023-10-27T10:01:06Z"}
{"event": "deploy:complete", "timestamp": "2023-10-27T10:02:15Z", "deployResult": {"manifests": ["deployment.yaml"]}}
{"event": "sync:pending", "timestamp": "2023-10-27T10:02:16Z"}
{"event": "sync:complete", "timestamp": "2023-10-27T10:02:17Z"}
{"event": "watch:file", "path": "Dockerfile", "timestamp": "2023-10-27T10:03:00Z"}
{"event": "build:artifact:pending", "id": "my-app", "timestamp": "2023-10-27T10:03:01Z"}

This stream of JSON events is Skaffold’s way of communicating its internal state transitions to the outside world. The event field tells you what just happened, and other fields provide context like the artifact ID, image name, timestamps, or deploy results. You can pipe this output to tools like jq to parse and react to these events programmatically.

The core problem Skaffold’s JSON output solves is enabling automated CI/CD pipelines and custom developer tooling to understand and react to the Skaffold development loop. Instead of relying on parsing terminal output (which is brittle and subject to change), you get structured, machine-readable events. This allows you to trigger subsequent actions based on build completion, deployment success, or even file changes detected by Skaffold’s watch mechanism.

Internally, Skaffold maintains a state machine representing the current phase of its operation (building, deploying, syncing, watching). When a transition occurs in this state machine – for example, an artifact build successfully completes, or Skaffold detects a change in a watched file – it emits a corresponding JSON event. The skaffold dev command, in particular, continuously loops through these phases, so you’ll see a continuous stream of events as you make changes to your code or configuration.

The event field is the primary key for understanding what’s happening. Common event prefixes include build, deploy, sync, and watch. For example, build:artifact:pending signifies the start of a build for a specific artifact, while build:artifact:complete indicates its successful completion, often providing the resulting image tag. deploy:complete signals that Skaffold has finished applying your application to the cluster.

If you’re using Skaffold for local development and want to trigger automated tests after a successful deployment, you’d monitor the deploy:complete events. You can use jq to filter for these:

skaffold dev --output-format=json | jq -c 'select(.event == "deploy:complete")'

This command will output only the JSON lines where the event field is exactly "deploy:complete". You could then pipe this further to a script that runs your integration tests.

The watch events are particularly useful for understanding what triggers a rebuild. When Skaffold detects a change in a file it’s watching (as defined in your skaffold.yaml’s watch or implicitly through the build context), it emits a watch:file event. This allows you to see precisely which file modification led to the next build cycle.

skaffold dev --output-format=json | jq -c 'select(.event | startswith("watch:"))'

This command will show you all events related to Skaffold’s watching mechanism, including watch:file and potentially others like watch:dir.

The deployResult field within a deploy:complete event is crucial for understanding what was deployed. It typically contains a list of manifests that Skaffold successfully applied. This information can be invaluable for subsequent steps that need to know the exact resources deployed, such as waiting for specific Kubernetes resources to become ready.

When Skaffold encounters an error during any phase, it emits an error event. This event will contain details about the failure, including a message and often a context. Parsing these error events is essential for building robust CI systems that can report failures accurately.

skaffold dev --output-format=json | jq -c 'select(.event == "error")'

This will capture any error events, allowing your CI system to fail the build or deployment.

The sync:complete event signifies that Skaffold has finished synchronizing files into your running containers. This is distinct from the build and deploy phases. If your workflow involves verifying that files have been successfully synced before proceeding, you’d monitor this event.

The most surprising thing about Skaffold’s JSON output is that it’s not a single, monolithic JSON document representing the entire state of skaffold dev at any given moment. Instead, it’s a continuous stream of individual JSON objects, each representing a discrete event or state change. This event-driven architecture is what makes it suitable for real-time processing by external tools.

The next logical step after parsing these events is to chain them together for more complex workflows. For instance, you might want to trigger a specific set of integration tests only after a successful deployment of a new image, and then use the deployResult to identify the specific Kubernetes resources to target with those tests.

Want structured learning?

Take the full Skaffold course →