diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a05422b5..a77b5c06 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -13,6 +13,7 @@ jobs:
     outputs:
       documentation: ${{ steps.filter.outputs.documentation }}
       cli: ${{ steps.filter.outputs.cli }}
+      go: ${{ steps.filter.outputs.go }}
     steps:
       - uses: actions/checkout@v4
         with:
diff --git a/commands/commands.go b/cmd/main.go
similarity index 97%
rename from commands/commands.go
rename to cmd/main.go
index 7b8c9b35..8fb9673a 100644
--- a/commands/commands.go
+++ b/cmd/main.go
@@ -1,4 +1,4 @@
-package commands
+package cmd
 
 import (
 	"fmt"
diff --git a/config.go b/config.go
new file mode 100644
index 00000000..be46c7f5
--- /dev/null
+++ b/config.go
@@ -0,0 +1,105 @@
+package main
+
+import (
+	"context"
+	"strings"
+
+	"github.com/mitchellh/go-homedir"
+	"github.com/sethvargo/go-envconfig"
+	"gopkg.in/ini.v1"
+)
+
+// Not sure how best to represent this enum type
+//type PluginRepositoryLastCheckDuration struct {
+//  Never bool
+//  Every int
+//}
+
+type Settings struct {
+	LegacyVersionFile bool
+	// I don't think this setting should be supported in the Golang implementation
+	//UseReleaseCandidates bool
+	AlwaysKeepDownload                bool
+	PluginRepositoryLastCheckDuration string
+	DisablePluginShortNameRepository  bool
+}
+
+type Config struct {
+	Home                        string
+	ConfigFile                  string `env:"ASDF_CONFIG_FILE"`
+	DefaultToolVersionsFilename string `env:"ASDF_DEFAULT_TOOL_VERSIONS_FILENAME"`
+	// Unclear if this value will be needed with the golang implementation.
+	//AsdfDir string
+	DataDir      string `env:"ASDF_DATA_DIR"`
+	ForcePrepend bool   `env:"ASDF_FORCE_PREPEND"`
+}
+
+func LoadConfig() (Config, error) {
+	config, err := loadConfigEnv()
+
+	if err != nil {
+		return config, err
+	}
+
+	homeDir, err := homedir.Dir()
+
+	if err != nil {
+		return config, err
+	}
+
+	config.Home = homeDir
+
+	return config, nil
+}
+
+func loadConfigEnv() (Config, error) {
+	context := context.Background()
+	config := Config{}
+
+	err := envconfig.Process(context, &config)
+
+	return config, err
+}
+
+func LoadSettings(asdfrcPath string) (Settings, error) {
+	// asdfrc is effectively formatted as ini
+	config, err := ini.Load(asdfrcPath)
+
+	if err != nil {
+		return Settings{}, err
+	}
+
+	mainConf := config.Section("")
+	checkDuration := stringToCheckDuration(mainConf.Key("plugin_repository_last_check_duration").String(), "60")
+
+	return Settings{
+		LegacyVersionFile:                 YesNoToBool(mainConf, "legacy_version_file", false),
+		AlwaysKeepDownload:                YesNoToBool(mainConf, "use_release_candidates", false),
+		PluginRepositoryLastCheckDuration: checkDuration,
+		DisablePluginShortNameRepository:  YesNoToBool(mainConf, "disable_plugin_short_name_repository", false),
+	}, nil
+}
+
+func YesNoToBool(section *ini.Section, key string, defaultValue bool) bool {
+	yesOrNo := section.Key(key).String()
+	lcYesOrNo := strings.ToLower(yesOrNo)
+	if lcYesOrNo == "yes" {
+		return true
+	}
+	if lcYesOrNo == "no" {
+		return false
+	}
+
+	return defaultValue
+}
+
+// Eventually this should return a custom type but I need to figure out how to
+// represent the (never string, duration int) type. For now it just returns a
+// string.
+func stringToCheckDuration(checkDuration string, defaultValue string) string {
+	if checkDuration != "" {
+		return checkDuration
+	}
+
+	return defaultValue
+}
diff --git a/config_test.go b/config_test.go
new file mode 100644
index 00000000..34d66f7c
--- /dev/null
+++ b/config_test.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+	"testing"
+)
+
+func TestLoadConfig(t *testing.T) {
+	config, err := LoadConfig()
+
+	assert(t, err == nil, "Returned error when building config")
+
+	assert(t, config.Home != "", "Expected Home to be set")
+}
+
+func TestLoadConfigEnv(t *testing.T) {
+	config, err := loadConfigEnv()
+
+	assert(t, err == nil, "Returned error when loading env for config")
+
+	assert(t, config.Home == "", "Shouldn't set Home property when loading config")
+}
+
+func TestLoadSettings(t *testing.T) {
+	t.Run("When given invalid path returns error", func(t *testing.T) {
+		_, err := LoadSettings("./foobar")
+
+		if err == nil {
+			t.Fatal("Didn't get an error")
+		}
+	})
+
+	t.Run("When given path to populated asdfrc returns populated settings struct", func(t *testing.T) {
+		settings, err := LoadSettings("testdata/asdfrc")
+
+		refuteError(t, err)
+
+		assert(t, settings.LegacyVersionFile == true, "LegacyVersionFile field has wrong value")
+		assert(t, settings.AlwaysKeepDownload == true, "AlwaysKeepDownload field has wrong value")
+		assert(t, settings.PluginRepositoryLastCheckDuration == "never", "PluginRepositoryLastCheckDuration field has wrong value")
+		assert(t, settings.DisablePluginShortNameRepository == true, "DisablePluginShortNameRepository field has wrong value")
+	})
+
+	t.Run("When given path to empty file returns settings struct with defaults", func(t *testing.T) {
+		settings, err := LoadSettings("testdata/empty-asdfrc")
+
+		refuteError(t, err)
+
+		assert(t, settings.LegacyVersionFile == false, "LegacyVersionFile field has wrong value")
+		assert(t, settings.AlwaysKeepDownload == false, "AlwaysKeepDownload field has wrong value")
+		assert(t, settings.PluginRepositoryLastCheckDuration == "60", "PluginRepositoryLastCheckDuration field has wrong value")
+		assert(t, settings.DisablePluginShortNameRepository == false, "DisablePluginShortNameRepository field has wrong value")
+	})
+}
+
+func assert(t *testing.T, expr bool, message string) {
+	if !expr {
+		t.Error(message)
+	}
+}
+
+func refuteError(t *testing.T, err error) {
+	if err != nil {
+		t.Fatal("Returned unexpected error", err)
+	}
+}
diff --git a/go.mod b/go.mod
index 5fbbb330..d91459ae 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,14 @@ module asdf
 go 1.21.5
 
 require (
-	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/mitchellh/go-homedir v1.1.0
+	github.com/sethvargo/go-envconfig v1.0.0
 	github.com/spf13/cobra v1.8.0
+	gopkg.in/ini.v1 v1.67.0
+)
+
+require (
+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/stretchr/testify v1.8.4 // indirect
 )
diff --git a/go.sum b/go.sum
index d0e8c2c3..858080f4 100644
--- a/go.sum
+++ b/go.sum
@@ -1,10 +1,25 @@
 github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sethvargo/go-envconfig v1.0.0 h1:1C66wzy4QrROf5ew4KdVw942CQDa55qmlYmw9FZxZdU=
+github.com/sethvargo/go-envconfig v1.0.0/go.mod h1:Lzc75ghUn5ucmcRGIdGQ33DKJrcjk4kihFYgSTBmjIc=
 github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
 github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
index e23893f4..e8e681cf 100644
--- a/main.go
+++ b/main.go
@@ -1,10 +1,10 @@
 package main
 
 import (
-	"asdf/commands"
+	"asdf/cmd"
 )
 
 // Placeholder for the real code
 func main() {
-	commands.Execute()
+	cmd.Execute()
 }
diff --git a/testdata/asdfrc b/testdata/asdfrc
new file mode 100644
index 00000000..d7e1c992
--- /dev/null
+++ b/testdata/asdfrc
@@ -0,0 +1,7 @@
+# This is a test asdfrc file containing all possible values. Each field to set
+# to a value that is different than the default.
+legacy_version_file = yes
+use_release_candidates = yes
+always_keep_download = yes
+plugin_repository_last_check_duration = never
+disable_plugin_short_name_repository = yes
diff --git a/testdata/empty-asdfrc b/testdata/empty-asdfrc
new file mode 100644
index 00000000..e69de29b