Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

The fastest practical way to learn Go is to build one command-line tool in progressively harder slices: start with command parsing, then add configuration, structured errors, tests, an API client, and container packaging. Choose Docker if you want a smaller first integration that controls images and containers through a daemon. Choose Kubernetes if you want deeper practice with typed resource APIs, kubeconfig and service-account authentication, namespaces, RBAC, and distributed-system state.

Choose the project that matches your learning goal

Both projects teach idiomatic Go, but they expose different engineering problems. A Docker tool usually talks to one local or remote Docker daemon. A Kubernetes tool talks to an API server whose responses depend on credentials, namespaces, permissions, resource versions, and the current state of a cluster.

Decision point Docker CLI Kubernetes CLI
API model Operations on images, containers, networks, and volumes through the Docker Engine daemon. Resource-oriented API operations on objects such as Pods, Deployments, Services, and Namespaces.
Authentication Access to the Docker daemon, commonly through environment variables, a local socket, or a remote endpoint. A kubeconfig file, the same file used by kubectl, or in-cluster service-account credentials.
Compatibility The Go client can negotiate an API version supported by both the client and daemon. Your client-go dependency should be aligned with the Kubernetes release you target.
Typical failures Daemon unavailable, permission denied, image not found, invalid container options, or an API-version mismatch. API-server connectivity, expired credentials, wrong namespace, RBAC denial, invalid resource specification, or a resource that has not reached the desired state.
Testing environment A local daemon is convenient for integration tests. Use mocked client interfaces for unit tests and a disposable cluster for integration tests.
Learning emphasis A compact first integration and clear request/response flow. Typed APIs, configuration loading, authentication, reconciliation of state, and distributed-system behavior.

If this is your first substantial Go program, implement a Docker command such as images or containers first. Move to Kubernetes after you can explain your package boundaries, error paths, tests, and configuration precedence. If your immediate work involves clusters, start with a read-only Kubernetes command such as pods and postpone mutating commands such as deploy until authentication and error handling are solid.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set up a small, repeatable Go project

Install only the prerequisites you need

  • A current Go toolchain compatible with the versions of the libraries you select.
  • Git, so you can review each learning slice as a separate commit.
  • For the Docker path, a reachable Docker Engine daemon and permission to query it.
  • For the Kubernetes path, access to a cluster and a kubeconfig that can list the resource you intend to read. In-cluster code additionally requires a service account with the necessary RBAC permissions.

Do not assume that a program that works on your laptop can authenticate in a container or in a CI job. Record the target Docker API behavior or Kubernetes release in your README and pin dependencies in go.mod. Upgrade them deliberately rather than allowing an unrelated update to change your client behavior.

#1 Best Overall
Sale
Nicpro Mechanical Carpenter Pencils for Construction (Black, Red) With Case
  • Valued Carpenter Pencil Set: You will get 2 pcs solid carpenter pencils with 26 piece 2.8 mm refills, 1 replaceable sharpener, 1 plastic storage box.The complete carpenter pencils combination allows you to finish your work faster and more easily
  • Deep Hole Marker Pencil: The deep-hole construction pencils adopts 45mm elongated tip design, which is more convenient to mark in the small hole or in other tight areas that other carpenter markers cannot reach
  • Carpenter Pencils with Sharpener: The sharpener is screwed into the top of the work pencil, which won't get lost either. Built-in pencil sharpener that keep the lead with pointed and smooth to Improves line of sight in fine work
  • Stronger Solid Lead: This work pencil is matched with a 2.8 mm thick lead , which is much thicker and stronger during the drawing process of construction work, it will not break or damage easily
  • Marks on Various Surfaces: 3 colors solid construction pencil can marks on various surfaces,such as metal, plastic, wood, paper etc. Ideals for woodworkers, contractors, craftsmen, builders, merchants and masons

Create the module and executable

mkdir platformctl
cd platformctl
go mod init example.com/platformctl
cat > main.go <<'EOF'
package main

import "fmt"

func main() {
	fmt.Println("platformctl")
}
EOF

go run .

The module path is an example; use the path you publish or the private path used by your organization. At this point, the program should compile without any platform SDK. Keeping the first commit dependency-free makes later failures easier to attribute.

Use a package layout that preserves boundaries

Keep command parsing and presentation separate from service code. A layout like this is sufficient for a first version:

platformctl/
  main.go
  internal/
    cli/
      root.go
      images.go
      pods.go
    config/
      config.go
    dockerapi/
      client.go
    kubernetesapi/
      client.go
    output/
      table.go
      json.go

The cli package translates arguments into an operation. The API packages translate that operation into Docker or Kubernetes calls. The output package knows how to render a stable table or JSON document, but it should not know how to authenticate to a cluster. This separation lets you test most behavior without a daemon or cluster.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build the command-line surface before adding an API

Start with explicit commands

Pick one useful read operation and define its contract before writing integration code. For example:

  • platformctl images lists Docker images.
  • platformctl containers lists Docker containers, optionally including stopped containers.
  • platformctl pods lists Kubernetes Pods in a namespace.
  • platformctl deploy creates or updates a Kubernetes Deployment after the read-only commands work.

Document the exit status, columns, and whether the command reads from standard input. A predictable interface is more valuable than a large command count.

Implement flags and environment variables

The standard library’s flag package is enough to learn parsing. You can adopt a larger command framework later without changing the API packages.

package config

import (
	"flag"
	"os"
)

type Config struct {
	Output   string
	Namespace string
	All      bool
}

func Parse(args []string) (Config, error) {
	fs := flag.NewFlagSet("platformctl", flag.ContinueOnError)
	output := fs.String("output", "table", "table or json")
	namespace := fs.String("namespace", "", "Kubernetes namespace")
	all := fs.Bool("all", false, "include stopped containers")
	if err := fs.Parse(args); err != nil {
		return Config{}, err
	}
	cfg := Config{Output: *output, Namespace: *namespace, All: *all}
	if cfg.Namespace == "" {
		cfg.Namespace = os.Getenv("PLATFORMCTL_NAMESPACE")
	}
	return cfg, nil
}

Define precedence once and apply it consistently. A practical order is built-in defaults, configuration file, environment variables, then command-line flags. If you later add a file, parse it into the same Config structure and record which source supplied each value when diagnostics matter. Reject unknown output formats and invalid combinations before making an API request.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make output useful to both people and scripts

Human-readable tables are convenient at a terminal; JSON is safer for automation. Keep field names and ordering stable, and send diagnostic messages to standard error so standard output remains machine-readable.

Rank #2
Sale
DEWALT 20V MAX Drill and Impact Driver (DCK240C2) | Includes 2 Batteries, Charger and Bag (
  • Ergonomically Designed: Work in tight areas with a compact design that gets into tough spots
  • Both tools are designed to fit into difficult to reach spaces
  • Complete a wide range of applications with a high-performance motor that delivers up to 300 UWO, and an impact driver delivering up to 1,400 in-lbs of torque
  • Both the DEWALT impact driver and drill driver feature integrated LED work lights with a convenient 20-second delay, ensuring enhanced visibility in dimly lit or challenging work areas
  • One-Handed Loading - Keep one hand free with a 1/4 in. hex chuck that accepts 1 in. bit tips
type ImageRow struct {
	Repository string `json:"repository"`
	Tag        string `json:"tag"`
	ID         string `json:"id"`
	Created    string `json:"created"`
}

func PrintJSON(v any, w io.Writer) error {
	enc := json.NewEncoder(w)
	enc.SetIndent("", "  ")
	return enc.Encode(v)
}

Do not make callers parse localized timestamps or decorative status text. If a value is unavailable, use an explicit JSON null or a documented empty field rather than silently changing the schema.

Add structured errors and exit codes early

Errors are part of your CLI’s interface. Wrap low-level errors with the operation and resource involved, while preserving the original error for inspection.

var (
	ErrUsage      = errors.New("usage error")
	ErrUnavailable = errors.New("service unavailable")
)

type OpError struct {
	Op   string
	Kind error
	Err  error
}

func (e *OpError) Error() string {
	return e.Op + ": " + e.Err.Error()
}

func (e *OpError) Unwrap() error { return e.Err }

Map categories to a small, documented set of process exit codes. For example, return one code for invalid arguments, another for authentication or permission failures, and another for unavailable services. Keep the exact mapping in one place in main; command functions should return errors rather than calling os.Exit, which makes them testable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
func run() error {
	// Parse the command, load configuration, and execute it.
	return nil
}

func main() {
	if err := run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		var oe *OpError
		switch {
		case errors.As(err, &oe) && errors.Is(oe.Kind, ErrUsage):
			os.Exit(2)
		case errors.As(err, &oe) && errors.Is(oe.Kind, ErrUnavailable):
			os.Exit( unavailabilityExitCode )
		default:
			os.Exit(1)
		}
	}
}

Replace unavailabilityExitCode with the numeric constant you choose; do not scatter unexplained numbers through command handlers. Include the mapping in --help or your documentation so scripts can react correctly.

Integrate Docker through the Engine API

Understand what the client is doing

Docker provides an API for interacting with the Docker daemon, along with SDKs for Go and Python. Your Go program is a client of that daemon; it is not itself a container runtime. The daemon may be local or remote, and its endpoint and permissions determine what the command can do.

Add the Docker Go module with:

go get github.com/docker/docker
go mod tidy

Pin the resulting dependency in go.mod and record the Docker API compatibility you support. The Go SDK can negotiate an API version supported by both client and daemon, which is useful when the daemon is newer or older than the library’s default.

Create a client with environment-based settings

package dockerapi

import (
	"context"

	"github.com/docker/docker/client"
)

func New() (*client.Client, error) {
	return client.NewClientWithOpts(
		client.FromEnv,
		client.WithAPIVersionNegotiation(),
	)
}

func Ping(ctx context.Context, cli *client.Client) error {
	_, err := cli.Ping(ctx)
	return err
}

client.FromEnv allows the standard Docker environment settings to select the endpoint and TLS behavior. Keep this choice in one constructor so tests can inject a client or a small interface. Always create a context with a timeout for network operations; a CLI that waits forever is difficult to diagnose in automation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Implement an images command as the first slice

func ListImages(ctx context.Context, cli *client.Client, all bool) ([]ImageRow, error) {
	images, err := cli.ImageList(ctx, image.ListOptions{All: all})
	if err != nil {
		return nil, fmt.Errorf("list images: %w", err)
	}
	rows := make([]ImageRow, 0, len(images))
	for _, img := range images {
		repository, tag := "<none>", "<none>"
		if len(img.RepoTags) > 0 {
			parts := strings.LastIndex(img.RepoTags[0], ":")
			if parts > 0 {
				repository, tag = img.RepoTags[0][:parts], img.RepoTags[0][parts+1:]
			}
		}
		rows = append(rows, ImageRow{
			Repository: repository,
			Tag:        tag,
			ID:         img.ID,
			Created:    time.Unix(img.Created, 0).UTC().Format(time.RFC3339),
		})
	}
	return rows, nil
}

In production code, import the Docker image types used by your selected SDK version and handle untagged images without assuming a repository tag exists. The important design is that this function returns data; table and JSON rendering happen elsewhere.

Rank #3
Sale
Push to Unlock,Katerk 6pcs 1/4 inch Hex Shank Aluminum Alloy Screwdriver Bit Holder Light-Weight Quick-Change Extension Bar Keychain Drill Screw Adapter Portable,Black Carabiner,Tool Gifts for Men
  • 【Great Compatibility】This Katerk 1/4 inch hex shank bit holder is specifically designed for 1/4 inch hex shank drill bits. It's compatible with most 1/4 fast hex handles, hex sockets, various electric screwdrivers, and handheld screwdrivers. The bit holder makes it a valuable addition for any handyman.
  • 【Secure and Safe】Built with a secure backup nut design, each drill bit holder securely locks onto your bits, ensuring they stay firmly in place. Additionally, our bit holder incorporates a high-quality steel ball rolling design that holds up to several kilograms of weight, ensuring your various drill bits don't fall off.
  • 【Easy One-Handed Operation】The bit holder for impact driver allows you to change bits single-handedly, simplifying your workflow. Its multi-color design further allows for quick identification of the drill bit you need.
  • 【Compact and Convenient】Thanks to its compact size, this 1/4 inch bit holder is easy to carry around. The bit holder allows for easy attachment to various tools, making this a convenient addition to your construction accessories. The Katerk bit holder is cast from high-quality alloy material, promising a long product lifespan. Despite its rugged strength, the bit holder remains lightweight, making it portable.
  • 【Cool Christmas Gift For Men Stocking Stuffers】 This screwdriver bit holder, driver bit holder, impact bit holder, can be given as a gift to your loved one, especially for anyone involved in construction or electrical work. It's a must-have for stocking stuffers for men and women, tools gifts for dad, tech gadgets for men, gifts for dad, gifts for him, gifts for husband, gifts for boyfriend, cool gadgets for men, and cool gifts for dad.

Handle Docker-specific failures

  • If the daemon socket or endpoint is unreachable, report the endpoint and suggest checking daemon status and permissions.
  • If API negotiation fails, inspect the daemon version and the SDK version recorded in go.mod.
  • If an image or container is missing, distinguish “not found” from a transport failure so scripts can react appropriately.
  • For mutating commands, show the resource identifier and operation in errors, and require an explicit confirmation or dry-run mode when deletion is involved.

Integrate Kubernetes with client-go

Align the dependency with your target release

k8s.io/client-go is the official Go client. It includes typed clients for known resources and also provides dynamic, discovery, and cache packages. Kubernetes releases are not interchangeable from a dependency perspective, so choose the Kubernetes release you support and use the corresponding release-aligned client-go tag. Add it explicitly and keep the selected version in go.mod:

go get k8s.io/client-go
go mod tidy

The command lets Go resolve a version; after it completes, inspect and pin the version rather than treating every future release as automatically compatible. When you deliberately upgrade Kubernetes support, update the module and run the complete test suite as one change.

Load a kubeconfig for a local or external cluster

The Kubernetes Go client can use the same kubeconfig file as the kubectl CLI to locate and authenticate to the API server. A typical loader checks an explicit path, then the conventional environment variable, then the user’s default configuration path.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
package kubernetesapi

import (
	"os"
	"path/filepath"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

func FromKubeconfig(path string) (kubernetes.Interface, error) {
	if path == "" {
		path = os.Getenv("KUBECONFIG")
	}
	if path == "" {
		home, err := os.UserHomeDir()
		if err != nil {
			return nil, err
		}
		path = filepath.Join(home, ".kube", "config")
	}
	cfg, err := clientcmd.BuildConfigFromFlags("", path)
	if err != nil {
		return nil, fmt.Errorf("load kubeconfig: %w", err)
	}
	clientset, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, fmt.Errorf("create Kubernetes client: %w", err)
	}
	return clientset, nil
}

Provide a --kubeconfig flag and make the selected context visible in verbose diagnostics. Never print bearer tokens, client certificates, or complete credential files.

Support in-cluster authentication deliberately

A program running inside Kubernetes can use service-account configuration instead of a user’s kubeconfig. Call rest.InClusterConfig(), create a clientset from that configuration, and ensure the Pod’s service account has an RBAC Role or ClusterRole allowing the exact verbs and resources your command needs.

func InCluster() (kubernetes.Interface, error) {
	cfg, err := rest.InClusterConfig()
	if err != nil {
		return nil, fmt.Errorf("load in-cluster configuration: %w", err)
	}
	clientset, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, fmt.Errorf("create in-cluster client: %w", err)
	}
	return clientset, nil
}

Do not silently fall back from a failed in-cluster configuration to a developer’s home directory. Select the authentication mode from an explicit flag or runtime setting so a production Pod cannot accidentally use credentials copied into its image.

List Pods with a typed client

func ListPods(ctx context.Context, clientset kubernetes.Interface, namespace string) ([]corev1.Pod, error) {
	list, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
	if err != nil {
		return nil, fmt.Errorf("list pods in namespace %q: %w", namespace, err)
	}
	return list.Items, nil
}

Use an empty namespace only when you intentionally request all namespaces and your permissions allow it. For a human-oriented command, display namespace, Pod name, phase, readiness, and age. For JSON, preserve stable field names and include the raw Kubernetes resource version only if your command needs it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Move from reads to state-changing operations

A deploy command teaches more than a list command, but it also introduces race conditions and partial failure. Validate the image, replicas, namespace, and name locally. Submit the Deployment, then decide whether the command merely reports acceptance or waits for readiness. If it waits, use a context deadline and poll status rather than sleeping indefinitely. Treat “API request accepted” and “desired state reached” as separate outcomes in both output and exit status.

Rank #4
Sale
2 Pack Carpenter Pencils Mechanical Pencils with 12 Refills, (2 Colors)
  • Long Nib and Deep Hole Marker: Our mechanical carpenter pencil with 45mm nib is designed for easy marking of deep holes or narrow areas. These construction pencils are the great choice for woodworking tools, construction tools, carpenter tools, contractor tools, wood carpentry tools and architect tools
  • Extra Refills in 2 Colors for Versatile Marking: The construction mechanical pencil comes with 12 extra 2.8mm refills, including 6 red and 6 black refills. The black refill is suitable for light surfaces, while the red wax is perfect for dark surfaces. Our carpenter mechanical pencil makes sure that you'll have an ample supply for extended use
  • Built-in Sharpener: Our construction pencil comes with a built-in sharpener to ensure the mechanical pencil tip is always sharp and ready for use. Never buy an extra pencil sharpener again. A great tool for any woodworker pencil, contractor pencils. The refill can easily be extended or retracted with a simple click of the pencils mechanical, allowing you to work more efficiently and accurately
  • Portable Clip Design: Our deep hole construction pencil features a portable clip design, easy to carry and attach to your pocket or tool box, so that you can keep the carpenter pencils mechanical close at hand, making it a convenient tool to have on the go. Great gifts choice for carpenters
  • Stronger Pencil Lead: The black refills are made of lead, sturdy and smooth. The red refills are made of wax, clear and light. These marking pencils are much thicker and stronger than normal pencils during the marking process of construction work, suitable for various surfaces, such as glasses, metal, boards, floors, walls, furniture, etc. The written marks can be easily wiped with a wet paper towel when needed
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Design tests around pure functions and replaceable clients

Unit-test configuration and formatting first

Configuration precedence, command validation, exit-code mapping, table formatting, JSON encoding, and resource-to-row conversion are deterministic and should run without Docker or Kubernetes.

func TestParseFlagOverridesEnvironment(t *testing.T) {
	t.Setenv("PLATFORMCTL_NAMESPACE", "from-env")
	cfg, err := Parse([]string{"-namespace", "from-flag"})
	if err != nil {
		t.Fatal(err)
	}
	if cfg.Namespace != "from-flag" {
		t.Fatalf("namespace = %q", cfg.Namespace)
	}
}

Include malformed flags, unsupported output modes, empty names, and JSON encoding errors. Table tests are useful because one input slice can cover tagged, untagged, and partially populated resources.

Inject interfaces for API behavior

Do not make every command depend directly on a concrete SDK client. Define the smallest interface the command needs, then pass a fake implementation in tests. For Kubernetes, the official client interfaces and fake clients can exercise list and create paths without a real API server. For Docker, wrap the SDK methods you use behind your own interface so an error or response can be returned deterministically.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Test a successful list and verify ordering and fields.
  • Return a permission error and verify the command maps it to the documented category.
  • Return a timeout or connection error and verify the message identifies the operation.
  • Verify that a cancelled context stops work and does not print a success line.

Add integration tests only where they add confidence

A local Docker daemon is suitable for integration tests that create and remove disposable images or containers. Isolate those tests with unique names, clean up in a deferred function, and skip them when the daemon endpoint is not configured.

Kubernetes integration tests need a disposable cluster or an isolated namespace. Create only the resources the test owns, wait for deletion, and avoid depending on a developer’s current context. Mocked clients prove command logic; a real cluster proves authentication, serialization, RBAC, and API-server behavior. You need both kinds for a state-changing tool.

Package the CLI in a Docker image

Use a multi-stage build

Docker’s Go guidance recommends starting from an official Go image and copying module files before downloading dependencies. That ordering lets Docker reuse the dependency layer when only source files change.

FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/platformctl .

FROM scratch
COPY --from=build /out/platformctl /platformctl
ENTRYPOINT ["/platformctl"]

Select the Go image tag and target architecture deliberately rather than copying this example unchanged. If your program needs certificate authorities, time-zone data, or CGO, a minimal scratch image may be inappropriate; use a small runtime image that contains the required files.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build and run without hiding credentials

docker build -t platformctl:dev .
docker run --rm platformctl:dev --help

A containerized Docker command needs a deliberate connection to the Docker daemon, such as a mounted Unix socket or a configured remote endpoint. Mounting a host socket grants powerful control over the host; document that risk and prefer a restricted remote endpoint or a purpose-built test daemon where possible.

Best Value
Milwaukee 48-22-3104 Inkzall Point Marker, Fine, Black, 4-Pack
  • Milwaukee Ink all Fine Point Marker, Black, 4 Per Pack
  • 4 per pack Features Clog Resistant Marker Tip Writes through Dusty, Wet and Oily Surfaces Durable Marker Tip for Writing on Concrete, OSB and Rough Surfaces
  • Clog resistant tip writes on dusty, wet and oily surfaces and is optimized for rough surfaces such as OSB, cinderblock and concrete
  • Hard hat clip- attaches for easy access
  • Quick dry time with reduced smearing and marking

A containerized Kubernetes command normally needs a mounted kubeconfig or in-cluster service-account credentials. Do not bake a kubeconfig, token, or client certificate into the image layer. Pass the file at runtime with read-only permissions, or deploy the CLI as a Pod whose service account has narrowly scoped RBAC.

A progressive learning plan that produces a usable tool

Slice What you implement Evidence that you are ready to continue
1. Executable Module, main.go, help text, and one command name. go run . and a clean build work from a fresh checkout.
2. Parsing Subcommands, flags, environment variables, and validation. Invalid input fails before any network call and produces a useful exit code.
3. Output Stable table and JSON renderers. Scripts can consume JSON without parsing terminal decorations.
4. Errors Wrapped operation errors, cancellation, timeouts, and exit-code mapping. Daemon, API-server, permission, and not-found failures are distinguishable.
5. Tests Pure-function unit tests and fake-client tests. Most tests run offline and cover success and failure branches.
6. API integration Docker Engine SDK or Kubernetes client-go. Credentials and target versions are explicit, and a read command works.
7. State changes Optional create, update, delete, or wait behavior. Deadlines, idempotency, confirmation, and partial failures are documented.
8. Packaging Multi-stage Docker build and runtime configuration. The image contains no credentials and can reach its intended endpoint.

Commit after every slice. When a later integration breaks, you can return to the last known-good boundary instead of debugging parsing, authentication, and output changes at the same time.

Troubleshoot the failures you are most likely to meet

“Connection refused” or “cannot connect to daemon”

Confirm that the Docker daemon is running, the endpoint selected by the environment is the one you intended, and the invoking user can access it. Run the smallest possible ping operation before debugging image-list logic. In a container, verify that the socket or remote endpoint was intentionally provided.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

“Unauthorized,” “forbidden,” or an empty Kubernetes list

Check the selected kubeconfig context, namespace, cluster address, and clock. A valid identity can still lack the verb required for a resource. Inspect RBAC bindings for the exact service account or user. An empty list is not proof that authentication failed; it may simply mean the namespace contains no matching resources.

Compile errors after changing client versions

Read the module versions in go.mod, consult the API for the release you target, and update all related dependencies as a deliberate change. Do not copy code written for an unrelated client-go or Docker SDK release without checking its method signatures and types.

The command hangs in CI

Apply a context deadline to every network request and make retry behavior bounded. Print progress or diagnostics to standard error, not standard output. In tests, ensure fake clients return promptly and that deferred cleanup cannot wait forever.

JSON output is polluted by logs

Send logs and warnings to standard error, and make the command’s standard output contain only the documented JSON document. Add a test that decodes captured standard output as JSON; this catches accidental prints from initialization code.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What to learn next

Once the basic tool is reliable, add completion scripts, pagination, label or name filters, configurable timeouts, retries with backoff, and structured logging. For Kubernetes, learn discovery before supporting arbitrary resource kinds, and study watches and caches before attempting a continuously running controller. For Docker, add container inspection, log streaming, and image pull progress only after you can cancel streams cleanly.

The project is successful when another user can install the binary, select the intended endpoint and credentials, understand its output, and recover from a failure without reading the source. That standard forces you to practice the Go skills that matter beyond this single CLI: small interfaces, explicit configuration, context-aware I/O, predictable errors, tests that do not depend on luck, and dependency upgrades you can explain.

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.