diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000000000000000000000000000000000000..10631f0665062750b2a1f2c4e538df4d6bc03f23
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,8 @@
+coverage:
+  range: 70..90 # First number represents red, and second represents green.
+  status:
+    patch: false
+    project:
+      default:
+        # Allow going dows 1% before being a failure.
+        threshold: 1%
\ No newline at end of file
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000000000000000000000000000000000..609f0d426adf0d3f37e1968b9c167c9289d64ade
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,2 @@
+*       @slok
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c751154c8089a145d3b442b7ed248c22a0f34a95
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,18 @@
+version: 2
+updates:
+  - package-ecosystem: "gomod"
+    directory: "/"
+    schedule:
+      interval: "daily"
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "daily"
+  - package-ecosystem: "docker"
+    directory: "/docker/dev"
+    schedule:
+      interval: "daily"
+  - package-ecosystem: "docker"
+    directory: "/docker/prod"
+    schedule:
+      interval: "daily"
\ No newline at end of file
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..33ae1be44ccba60af448c3885ae252ea0dfaa9a8
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,96 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+  check:
+    name: Check
+    runs-on: ubuntu-latest
+    # Execute the checks inside the container instead the VM.
+    container: golangci/golangci-lint:v1.37.1-alpine
+    steps:
+      - uses: actions/checkout@v2
+      - run: ./scripts/check/check.sh
+
+  unit-test:
+    name: Unit test
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-go@v2
+        with:
+          go-version: 1.16
+      - run: make ci-test
+      - uses: codecov/codecov-action@v1
+        with:
+          token: ${{ secrets.CODECOV_UPLOAD_TOKEN }}
+          file: ./.test_coverage.txt
+          fail_ci_if_error: false
+
+  rolling-release-image:
+    # Only on main branch.
+    if: startsWith(github.ref, 'refs/heads/main')
+    env:
+      TAG_IMAGE_LATEST: "true"
+      PROD_IMAGE_NAME: ${GITHUB_REPOSITORY}
+      VERSION: ${GITHUB_SHA}
+    needs: [check, unit-test]
+    name: Release image
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Build image
+        run: make build-image
+      - name: Docker login
+        run: docker login ${DOCKER_HOST} -u ${DOCKER_USER} -p ${DOCKER_TOKEN}
+        env:
+          DOCKER_HOST: ""
+          DOCKER_USER: slok
+          DOCKER_TOKEN: ${{secrets.DOCKER_HUB_TOKEN}}
+      - name: Publish image
+        run: make publish-image
+
+  tagged-release-image:
+    # Only on tags.
+    if: startsWith(github.ref, 'refs/tags/')
+    env:
+      PROD_IMAGE_NAME: ${GITHUB_REPOSITORY}
+    needs: [check, unit-test]
+    name: Tagged release image
+    runs-on: ubuntu-latest
+    steps:
+      - run: echo "VERSION=${GITHUB_REF#refs/*/}" >> ${GITHUB_ENV} # Sets VERSION env var.
+      - uses: actions/checkout@v2
+      - name: Build image
+        run: make build-image
+      - name: Docker login
+        run: docker login ${DOCKER_HOST} -u ${DOCKER_USER} -p ${DOCKER_TOKEN}
+        env:
+          DOCKER_HOST: ""
+          DOCKER_USER: slok
+          DOCKER_TOKEN: ${{secrets.DOCKER_HUB_TOKEN}}
+      - name: Publish image
+        run: make publish-image
+
+  tagged-release-binaries:
+    # Only on tags.
+    if: startsWith(github.ref, 'refs/tags/')
+    needs: [check, unit-test]
+    name: Tagged release binaries
+    runs-on: ubuntu-latest
+    steps:
+      - run: echo "VERSION=${GITHUB_REF#refs/*/}" >> ${GITHUB_ENV} # Sets VERSION env var.
+      - uses: actions/checkout@v2
+      - name: Build binaries
+        run: |
+          mkdir -p ./bin
+          chmod -R 0777 ./bin
+          make build-all
+      - name: Upload binaries
+        uses: xresloader/upload-to-github-release@v1.3.0
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          file: "bin/*"
+          tags: true
+          draft: true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..9b746dd29679b4e1d09315eff65e263c0fb417bb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Vendor directory
+vendor/
+
+# Test coverage.
+.test_coverage.txt
+
+# Binaries
+bin/
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1e51c291d3c13c840023d6767eb6995863116b20
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,5 @@
+---
+
+run:
+  build-tags:
+    - integration
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..2d5331af9de02a82c372d69bad0b3b882243a200
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,6 @@
+# Changelog
+
+## [Unreleased]
+
+[unreleased]: https://github.com/slok/sloth/compare/v0.1.0...HEAD
+[v0.1.0]: https://github.com/slok/sloth/releases/tag/v0.1.0
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..10f6571ffee0d9c96eec8853cdfdbd4b3f2c0b57
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [2021] [Xabier Larrakoetxea]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..a1a8560174f9a400a9d0fda61008bb97b40db688
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,86 @@
+
+SHELL := $(shell which bash)
+OSTYPE := $(shell uname)
+DOCKER := $(shell command -v docker)
+GID := $(shell id -g)
+UID := $(shell id -u)
+VERSION ?= $(shell git describe --tags --always)
+
+UNIT_TEST_CMD := ./scripts/check/unit-test.sh
+INTEGRATION_TEST_CMD := ./scripts/check/integration-test.sh
+CHECK_CMD := ./scripts/check/check.sh
+
+DEV_IMAGE_NAME := slok/sloth-dev
+PROD_IMAGE_NAME ?=  slok/sloth
+
+DOCKER_RUN_CMD := docker run --env ostype=$(OSTYPE) -v ${PWD}:/src --rm ${DEV_IMAGE_NAME}
+BUILD_BINARY_CMD := VERSION=${VERSION} ./scripts/build/build.sh
+BUILD_BINARY_ALL_CMD := VERSION=${VERSION} ./scripts/build/build-all.sh
+BUILD_DEV_IMAGE_CMD := IMAGE=${DEV_IMAGE_NAME} DOCKER_FILE_PATH=./docker/dev/Dockerfile VERSION=latest ./scripts/build/build-image.sh
+BUILD_PROD_IMAGE_CMD := IMAGE=${PROD_IMAGE_NAME} DOCKER_FILE_PATH=./docker/prod/Dockerfile VERSION=${VERSION} ./scripts/build/build-image.sh
+PUBLISH_PROD_IMAGE_CMD := IMAGE=${PROD_IMAGE_NAME} VERSION=${VERSION} ./scripts/build/publish-image.sh
+
+
+help: ## Show this help
+	@echo "Help"
+	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "    \033[36m%-20s\033[93m %s\n", $$1, $$2}'
+
+.PHONY: default
+default: help
+
+.PHONY: build-image
+build-image: ## Builds the production docker image.
+	@$(BUILD_PROD_IMAGE_CMD)
+
+.PHONY: publish-image
+publish-image: ##Publishes the production docker image.
+	@$(PUBLISH_PROD_IMAGE_CMD)
+
+.PHONY: build-dev-image
+build-dev-image:  ## Builds the development docker image.
+	@$(BUILD_DEV_IMAGE_CMD)
+
+build: build-dev-image ## Builds the production binary.
+	@$(DOCKER_RUN_CMD) /bin/sh -c '$(BUILD_BINARY_CMD)'
+
+build-all: build-dev-image ## Builds all archs production binaries.
+	@$(DOCKER_RUN_CMD) /bin/sh -c '$(BUILD_BINARY_ALL_CMD)'
+
+.PHONY: test
+test: build-dev-image  ## Runs unit test.
+	@$(DOCKER_RUN_CMD) /bin/sh -c '$(UNIT_TEST_CMD)'
+
+.PHONY: check
+check: build-dev-image  ## Runs checks.
+	@$(DOCKER_RUN_CMD) /bin/sh -c '$(CHECK_CMD)'
+
+.PHONY: integration
+integration: build-dev-image ## Runs integration test.
+	@$(DOCKER_RUN_CMD) /bin/sh -c '$(INTEGRATION_TEST_CMD)'
+
+.PHONY: go-gen
+go-gen: build-dev-image  ## Generates go based code.
+	@$(DOCKER_RUN_CMD) /bin/sh -c './scripts/gogen.sh'
+
+.PHONY: gen
+gen: go-gen ## Generates all.
+
+.PHONY: deps
+deps:  ## Fixes the dependencies
+	@$(DOCKER_RUN_CMD) /bin/sh -c './scripts/deps.sh'
+
+.PHONY: ci-build
+ci-build: ## Builds the production binary in CI environment (without docker).
+	@$(BUILD_BINARY_CMD)
+
+.PHONY: ci-unit-test
+ci-test:  ## Runs unit test in CI environment (without docker).
+	@$(UNIT_TEST_CMD)
+
+.PHONY: ci-check
+ci-check:  ## Runs checks in CI environment (without docker).
+	@$(CHECK_CMD)
+
+.PHONY: ci-integration-test
+ci-integration: ## Runs integraton test in CI environment (without docker).
+	@$(INTEGRATION_TEST_CMD)
diff --git a/cmd/sloth/commands/init.go b/cmd/sloth/commands/init.go
new file mode 100644
index 0000000000000000000000000000000000000000..53c1ac5f63494cf92200d7f01295bddd8844409d
--- /dev/null
+++ b/cmd/sloth/commands/init.go
@@ -0,0 +1,21 @@
+package commands
+
+import (
+	"context"
+	"fmt"
+
+	"gopkg.in/alecthomas/kingpin.v2"
+)
+
+type generateCommand struct{}
+
+// NewGenerateCommand returns the generate command.
+func NewGenerateCommand(app *kingpin.Application) Command {
+	app.Command("generate", "Generates SLOs.")
+	return generateCommand{}
+}
+
+func (g generateCommand) Name() string { return "generate" }
+func (g generateCommand) Run(ctx context.Context, config RootConfig) error {
+	return fmt.Errorf("not implemented")
+}
diff --git a/cmd/sloth/commands/root.go b/cmd/sloth/commands/root.go
new file mode 100644
index 0000000000000000000000000000000000000000..9be32e58f2fa3cfd8f5081dd0966df47ceb387e1
--- /dev/null
+++ b/cmd/sloth/commands/root.go
@@ -0,0 +1,53 @@
+package commands
+
+import (
+	"context"
+	"io"
+
+	"gopkg.in/alecthomas/kingpin.v2"
+
+	"github.com/slok/sloth/internal/log"
+)
+
+const (
+	// LoggerTypeDefault is the logger default type.
+	LoggerTypeDefault = "default"
+	// LoggerTypeJSON is the logger json type.
+	LoggerTypeJSON = "json"
+)
+
+// Command represents an application command, all commands that want to be executed
+// should implement and setup on main.
+type Command interface {
+	Name() string
+	Run(ctx context.Context, config RootConfig) error
+}
+
+// RootConfig represents the root command configuration and global configuration
+// for all the commands.
+type RootConfig struct {
+	// Global flags.
+	Debug      bool
+	NoLog      bool
+	NoColor    bool
+	LoggerType string
+
+	// Global instances.
+	Stdin  io.Reader
+	Stdout io.Writer
+	Stderr io.Writer
+	Logger log.Logger
+}
+
+// NewRootConfig initializes the main root configuration
+func NewRootConfig(app *kingpin.Application) *RootConfig {
+	c := &RootConfig{}
+
+	// Register.
+	app.Flag("debug", "Enable debug mode.").BoolVar(&c.Debug)
+	app.Flag("no-log", "Disable logger.").BoolVar(&c.NoLog)
+	app.Flag("no-color", "Disable logger color.").BoolVar(&c.NoColor)
+	app.Flag("logger", "Selects the logger type.").Default(LoggerTypeDefault).EnumVar(&c.LoggerType, LoggerTypeDefault, LoggerTypeJSON)
+
+	return c
+}
diff --git a/cmd/sloth/main.go b/cmd/sloth/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..614320fe769f6ff4321347bc62f97c36dae5bfb8
--- /dev/null
+++ b/cmd/sloth/main.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"os"
+
+	"github.com/sirupsen/logrus"
+	"gopkg.in/alecthomas/kingpin.v2"
+
+	"github.com/slok/sloth/cmd/sloth/commands"
+	"github.com/slok/sloth/internal/log"
+	loglogrus "github.com/slok/sloth/internal/log/logrus"
+)
+
+// Version is the application version.
+var Version = "dev"
+
+// Run runs the main application.
+func Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error {
+	app := kingpin.New("sloth", "Easy SLO generator.")
+	app.Version(Version)
+	app.DefaultEnvars()
+	config := commands.NewRootConfig(app)
+
+	// Setup commands (registers flags).
+	generateCmd := commands.NewGenerateCommand(app)
+
+	cmds := map[string]commands.Command{
+		generateCmd.Name(): generateCmd,
+	}
+
+	// Parse commandline.
+	cmdName, err := app.Parse(args[1:])
+	if err != nil {
+		return fmt.Errorf("invalid command configuration: %w", err)
+	}
+
+	// Set up global dependencies.
+	config.Stdin = stdin
+	config.Stdout = stdout
+	config.Stderr = stderr
+	config.Logger = getLogger(*config, stderr)
+
+	// Execute command.
+	err = cmds[cmdName].Run(ctx, *config)
+	if err != nil {
+		return fmt.Errorf("%q command failed: %w", cmdName, err)
+	}
+
+	return nil
+}
+
+// getLogger returns the application logger.
+func getLogger(config commands.RootConfig, stderr io.Writer) log.Logger {
+	if config.NoLog {
+		return log.Noop
+	}
+
+	// If not logger disabled use logrus logger.
+	logrusLog := logrus.New()
+	logrusLog.Out = stderr // By default logger goes to stderr (so it can split stdout prints).
+	logrusLogEntry := logrus.NewEntry(logrusLog)
+
+	if config.Debug {
+		logrusLogEntry.Logger.SetLevel(logrus.DebugLevel)
+	}
+
+	// Log format.
+	switch config.LoggerType {
+	case commands.LoggerTypeDefault:
+		logrusLogEntry.Logger.SetFormatter(&logrus.TextFormatter{
+			ForceColors:   !config.NoColor,
+			DisableColors: config.NoColor,
+		})
+	case commands.LoggerTypeJSON:
+		logrusLogEntry.Logger.SetFormatter(&logrus.JSONFormatter{})
+	}
+
+	logger := loglogrus.NewLogrus(logrusLogEntry).WithValues(log.Kv{
+		"version": Version,
+	})
+
+	logger.Debugf("Debug level is enabled") // Will log only when debug enabled.
+
+	return logger
+}
+
+func main() {
+	ctx := context.Background()
+	err := Run(ctx, os.Args, os.Stdin, os.Stdout, os.Stderr)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error: %s", err)
+		os.Exit(1)
+	}
+}
diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..82471b1f017c56e754054cdf88b81f648b742914
--- /dev/null
+++ b/docker/dev/Dockerfile
@@ -0,0 +1,39 @@
+FROM golang:1.16
+
+ARG GOLANGCI_LINT_VERSION="1.37.1"
+ARG MOCKERY_VERSION="2.5.1"
+ARG ostype=Linux
+
+RUN apt-get update && apt-get install -y \
+    git \
+    bash \
+    zip
+
+
+RUN wget https://github.com/golangci/golangci-lint/releases/download/v${GOLANGCI_LINT_VERSION}/golangci-lint-${GOLANGCI_LINT_VERSION}-linux-amd64.tar.gz && \
+    tar zxvf golangci-lint-${GOLANGCI_LINT_VERSION}-linux-amd64.tar.gz --strip 1 -C /usr/local/bin/ && \
+    rm golangci-lint-${GOLANGCI_LINT_VERSION}-linux-amd64.tar.gz && \
+    \
+    wget https://github.com/vektra/mockery/releases/download/v${MOCKERY_VERSION}/mockery_${MOCKERY_VERSION}_Linux_x86_64.tar.gz && \
+    tar zxvf mockery_${MOCKERY_VERSION}_Linux_x86_64.tar.gz -C /tmp && \
+    mv /tmp/mockery /usr/local/bin/ && \
+    rm mockery_${MOCKERY_VERSION}_Linux_x86_64.tar.gz
+
+# Create user.
+ARG uid=1000
+ARG gid=1000
+
+RUN bash -c 'if [ ${ostype} == Linux ]; then addgroup -gid $gid app; else addgroup app; fi && \
+    adduser --disabled-password -uid $uid --ingroup app --gecos "" app && \
+    chown app:app -R /go'
+
+# Fill go mod cache.
+RUN mkdir /tmp/cache
+COPY go.mod /tmp/cache
+COPY go.sum /tmp/cache
+RUN chown app:app -R /tmp/cache
+USER app
+RUN cd /tmp/cache && \
+    go mod download
+
+WORKDIR /src
diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..1d819b62078ed60baaa8226a24fdce77b1e79bbd
--- /dev/null
+++ b/docker/prod/Dockerfile
@@ -0,0 +1,27 @@
+FROM golang:1.16-alpine as build-stage
+
+RUN apk --no-cache add \
+    g++ \
+    git \
+    make \
+    curl \
+    bash
+
+ARG VERSION
+ENV VERSION=${VERSION}
+
+# Compile.
+WORKDIR /src
+COPY . .
+RUN ./scripts/build/build.sh
+
+
+FROM alpine:latest
+
+RUN apk --no-cache add \
+    ca-certificates \
+    bash
+
+COPY --from=build-stage /src/bin/sloth /usr/local/bin/sloth
+
+ENTRYPOINT ["/usr/local/bin/sloth"]
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..2d423c65dc5154ca003d8d906642da11e856d876
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,11 @@
+module github.com/slok/sloth
+
+go 1.16
+
+require (
+	github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
+	github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
+	github.com/sirupsen/logrus v1.8.1
+	github.com/stretchr/testify v1.7.0 // indirect
+	gopkg.in/alecthomas/kingpin.v2 v2.2.6
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..bc98665a934da93f59c925e622acb0025c064599
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,24 @@
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
+github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/log/log.go b/internal/log/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..1c6539570cd29375c7b6b8cc087f213c09238970
--- /dev/null
+++ b/internal/log/log.go
@@ -0,0 +1,66 @@
+package log
+
+import "context"
+
+// Kv is a helper type for structured logging fields usage.
+type Kv = map[string]interface{}
+
+// Logger is the interface that the loggers used by the library will use.
+type Logger interface {
+	Infof(format string, args ...interface{})
+	Warningf(format string, args ...interface{})
+	Errorf(format string, args ...interface{})
+	Debugf(format string, args ...interface{})
+	WithValues(values map[string]interface{}) Logger
+	WithCtxValues(ctx context.Context) Logger
+	SetValuesOnCtx(parent context.Context, values map[string]interface{}) context.Context
+}
+
+// Noop logger doesn't log anything.
+const Noop = noop(0)
+
+type noop int
+
+func (n noop) Infof(format string, args ...interface{})                         {}
+func (n noop) Warningf(format string, args ...interface{})                      {}
+func (n noop) Errorf(format string, args ...interface{})                        {}
+func (n noop) Debugf(format string, args ...interface{})                        {}
+func (n noop) WithValues(map[string]interface{}) Logger                         { return n }
+func (n noop) WithCtxValues(context.Context) Logger                             { return n }
+func (n noop) SetValuesOnCtx(parent context.Context, values Kv) context.Context { return parent }
+
+type contextKey string
+
+// contextLogValuesKey used as unique key to store log values in the context.
+const contextLogValuesKey = contextKey("internal-log")
+
+// CtxWithValues returns a copy of parent in which the key values passed have been
+// stored ready to be used using log.Logger.
+func CtxWithValues(parent context.Context, kv Kv) context.Context {
+	// Maybe we have values already set.
+	oldValues, ok := parent.Value(contextLogValuesKey).(Kv)
+	if !ok {
+		oldValues = Kv{}
+	}
+
+	// Copy old and received values into the new kv.
+	newValues := Kv{}
+	for k, v := range oldValues {
+		newValues[k] = v
+	}
+	for k, v := range kv {
+		newValues[k] = v
+	}
+
+	return context.WithValue(parent, contextLogValuesKey, newValues)
+}
+
+// ValuesFromCtx gets the log Key values from a context.
+func ValuesFromCtx(ctx context.Context) Kv {
+	values, ok := ctx.Value(contextLogValuesKey).(Kv)
+	if !ok {
+		return Kv{}
+	}
+
+	return values
+}
diff --git a/internal/log/logrus/logrus.go b/internal/log/logrus/logrus.go
new file mode 100644
index 0000000000000000000000000000000000000000..efa25bdf0a2acc1ea462e50adf243dd49812462f
--- /dev/null
+++ b/internal/log/logrus/logrus.go
@@ -0,0 +1,31 @@
+package logrus
+
+import (
+	"context"
+
+	"github.com/sirupsen/logrus"
+
+	"github.com/slok/sloth/internal/log"
+)
+
+type logger struct {
+	*logrus.Entry
+}
+
+// NewLogrus returns a new log.Logger for a logrus implementation.
+func NewLogrus(l *logrus.Entry) log.Logger {
+	return logger{Entry: l}
+}
+
+func (l logger) WithValues(kv log.Kv) log.Logger {
+	newLogger := l.Entry.WithFields(kv)
+	return NewLogrus(newLogger)
+}
+
+func (l logger) WithCtxValues(ctx context.Context) log.Logger {
+	return l.WithValues(log.ValuesFromCtx(ctx))
+}
+
+func (l logger) SetValuesOnCtx(parent context.Context, values log.Kv) context.Context {
+	return log.CtxWithValues(parent, values)
+}
diff --git a/scripts/build/build-all.sh b/scripts/build/build-all.sh
new file mode 100755
index 0000000000000000000000000000000000000000..df5554613e7fc8268803cc4c0741b2663fc288e8
--- /dev/null
+++ b/scripts/build/build-all.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+set -o errexit
+set -o nounset
+
+# Build all.
+ostypes=("Linux" "Darwin" "Windows" "ARM")
+for ostype in "${ostypes[@]}"
+do
+	ostype="${ostype}" ./scripts/build/build.sh
+done
+
+# Create checksums.
+checksums_dir="./bin"
+cd ${checksums_dir} && sha256sum * > ./checksums.txt
diff --git a/scripts/build/build-image.sh b/scripts/build/build-image.sh
new file mode 100755
index 0000000000000000000000000000000000000000..90e9d3565418651dd44b8ce925ff935198a3efcd
--- /dev/null
+++ b/scripts/build/build-image.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env sh
+
+set -e
+
+if [ -z ${VERSION} ]; then
+    echo "IMAGE_VERSION env var needs to be set"
+    exit 1
+fi
+
+if [ -z ${IMAGE} ]; then
+    echo "IMAGE env var needs to be set"
+    exit 1
+fi
+
+if [ -z ${DOCKER_FILE_PATH} ]; then
+    echo "DOCKER_FILE_PATH env var needs to be set"
+    exit 1
+fi
+
+echo "Building image ${IMAGE}:${VERSION}..."
+docker build \
+    --build-arg VERSION=${VERSION} \
+    -t ${IMAGE}:${VERSION} \
+    -f ${DOCKER_FILE_PATH} .
+
+if [ ! -z ${TAG_IMAGE_LATEST} ]; then
+    echo "Tagged image ${IMAGE}:${VERSION} with ${IMAGE}:latest"
+    docker tag ${IMAGE}:${VERSION} ${IMAGE}:latest
+fi
\ No newline at end of file
diff --git a/scripts/build/build.sh b/scripts/build/build.sh
new file mode 100755
index 0000000000000000000000000000000000000000..47f7f77963b9df6489d2ee559d1fb9a28e1844db
--- /dev/null
+++ b/scripts/build/build.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+set -o errexit
+set -o nounset
+
+src=./cmd/sloth
+out=./bin/sloth
+
+ostype=${ostype:-"native"}
+
+function build() {
+    ext="${1:-}"
+    goos="${2:-}"
+    goarch="${3:-}"
+    goarm="${4:-}"
+
+    [[ ! -z "${goos}" ]] && export GOOS="${goos}"
+    [[ ! -z "${goarch}" ]] && export GOARCH="${goarch}"
+    [[ ! -z "${goarm}" ]] && export GOARM="${goarm}"
+
+    final_out=${out}${ext}
+    ldf_cmp="-s -w -extldflags '-static'"
+    f_ver="-X main.Version=${VERSION:-dev}"
+
+    echo "Building binary at ${final_out} (GOOS=${GOOS:-}, GOARCH=${GOARCH:-}, GOARM=${GOARM:-}, VERSION=${VERSION:-})"
+    CGO_ENABLED=0 go build -o ${final_out} --ldflags "${ldf_cmp} ${f_ver}"  ${src}
+}
+
+
+if [ $ostype == 'Linux' ]; then
+    build "-linux-amd64" "linux" "amd64"
+elif [ $ostype == 'Darwin' ]; then
+    build "-darwin-amd64" "darwin" "amd64"
+    build "-darwin-arm64" "darwin" "arm64"
+elif [ $ostype == 'Windows' ]; then
+    build "-windows-amd64.exe" "windows" "amd64"
+elif [ $ostype == 'ARM' ]; then
+    build "-linux-arm64" "linux" "arm64"
+    build "-linux-arm-v7" "linux" "arm" "7"
+else
+    # Native.
+    build
+fi
diff --git a/scripts/build/publish-image.sh b/scripts/build/publish-image.sh
new file mode 100755
index 0000000000000000000000000000000000000000..76fbbdc8dc327bd993abad9a3d619cdd8316cbcb
--- /dev/null
+++ b/scripts/build/publish-image.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env sh
+
+set -e
+
+if [ -z ${VERSION} ]; then
+    echo "IMAGE_VERSION env var needs to be set"
+    exit 1
+fi
+
+if [ -z ${IMAGE} ]; then
+    echo "IMAGE env var needs to be set"
+    exit 1
+fi
+
+echo "Pushing image ${IMAGE}:${VERSION}..."
+docker push ${IMAGE}:${VERSION}
+
+if [ ! -z ${TAG_IMAGE_LATEST} ]; then
+    echo "Pushing image ${IMAGE}:latest..."
+    docker push ${IMAGE}:latest
+fi
\ No newline at end of file
diff --git a/scripts/check/check.sh b/scripts/check/check.sh
new file mode 100755
index 0000000000000000000000000000000000000000..532753e82c780c7f02ed5ac651a2fc0cb6cf75ed
--- /dev/null
+++ b/scripts/check/check.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+set -o errexit
+set -o nounset
+
+golangci-lint run -E goimports --timeout 3m
\ No newline at end of file
diff --git a/scripts/check/integration-test.sh b/scripts/check/integration-test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f0d9c941fe7dba480fb58ca103840b0485529beb
--- /dev/null
+++ b/scripts/check/integration-test.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+set -o errexit
+set -o nounset
+
+go test -race -tags='integration' -v ./tests/integration/...
\ No newline at end of file
diff --git a/scripts/check/unit-test.sh b/scripts/check/unit-test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..b3b3a088d9dadb8218a7505e3c6b695c9d69a678
--- /dev/null
+++ b/scripts/check/unit-test.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env sh
+
+set -o errexit
+set -o nounset
+
+go test -race -coverprofile=.test_coverage.txt ./...
+go tool cover -func=.test_coverage.txt | tail -n1 | awk '{print "Total test coverage: " $3}'
\ No newline at end of file
diff --git a/scripts/deps.sh b/scripts/deps.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f11203913f6115e84617adfb80e131faba965ef3
--- /dev/null
+++ b/scripts/deps.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+set -o errexit
+set -o nounset
+
+go mod tidy
\ No newline at end of file
diff --git a/scripts/gogen.sh b/scripts/gogen.sh
new file mode 100755
index 0000000000000000000000000000000000000000..803ac8a6723c49f0f27bc6b58b70ba60be9d4d80
--- /dev/null
+++ b/scripts/gogen.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+set -o errexit
+set -o nounset
+
+go generate ./...
\ No newline at end of file