diff --git a/apps/app-of-apps.yaml b/apps/app-of-apps.yaml
new file mode 100644
index 000000000..7ec7e1293
--- /dev/null
+++ b/apps/app-of-apps.yaml
@@ -0,0 +1,23 @@
+---
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+ name: app-of-apps
+ namespace: argocd
+ finalizers:
+ - resources-finalizer.argocd.argoproj.io
+spec:
+ destination:
+ namespace: argocd
+ server: https://kubernetes.default.svc
+ project: default
+ source:
+ path: clusters/${DEPLOY_NAME}
+ repoURL: ${UC_DEPLOY_GIT_URL}
+ targetRevision: main
+ directory:
+ recurse: true
+ syncPolicy:
+ automated:
+ prune: true
+ selfHeal: true
diff --git a/docs/gitops-install.md b/docs/gitops-install.md
new file mode 100644
index 000000000..81e343465
--- /dev/null
+++ b/docs/gitops-install.md
@@ -0,0 +1,144 @@
+# GitOps based Install
+
+This guide is not meant to be a definitive guide to [GitOps][gitops] and
+how it can be used with UnderStack or even a best practices example
+but instead focused on an example development oriented installation.
+It will make a few assumptions and some opinionated choices that may
+not align with a production best practices installation.
+Most notable assumptions are:
+
+- [GitOps][gitops] tooling runs on the same cluster as the deploy
+- AIO (All-in-One) configuration
+- Your cluster is a blank slate and can be entirely consumed
+
+You will have the source to your deployment and all the pre-deployment
+work will occur on your local machine and not on any of the target
+machines.
+
+## Getting the source
+
+You must fetch the source to this repo and since we will be using
+[GitOps][gitops], you must also have a deployment repo. These
+operations can all happen locally on your development machine.
+
+```bash
+git clone https://github.com/rackerlabs/understack
+# then either
+git init uc-deploy
+# or
+git clone https://path/to/my/uc-deploy
+```
+
+## Pre-deployment
+
+Embracing GitOps and declarative configuration, we will define three
+distinct pieces of information for your deployment.
+
+- Infrastructure: Where the software will live (TODO: this defines the cluster)
+- Secrets: What are all the credentials, passwords, etc needed by the software
+- Cluster: The actual software that will be deployed
+
+To properly scope this you'll need an environment name. For the
+purposes of this document we'll call it `my-k3s`.
+
+### Environment Variables
+
+To avoid defining many environment variables we'll simplify by creating an
+`.env` file for our deployment. In this case we'll call it `my-k3s.env` and
+place it where we've cloned understack.
+
+```bash title="/path/to/understack/my-k3s.env"
+UC_REPO="/path/to/understack"
+UC_DEPLOY="/path/to/uc-deploy"
+DEPLOY_NAME="my-k3s"
+```
+
+### Remaining pre-deployment config
+
+ArgoCD will need to know where it can access your deployment config
+repo. This can be over SSH with a key or over HTTPS. Add to your
+`my-k3s.env` the following:
+
+```bash title="/path/to/understack/my-k3s.env"
+UC_DEPLOY_GIT_URL="git@github.com:my-org/uc-deploy.git"
+UC_DEPLOY_SSH_FILE="/path/to/ssh.private"
+```
+
+All services will utilize unique DNS names. The facilitate this, UnderStack
+will take a domain and add sub-domains for them. Add to your `my-k3s.env`
+the following:
+
+```bash title="/path/to/understack/my-k3s.env"
+DNS_ZONE="some.domain.corp"
+```
+
+### Populating the infrastructure
+
+TODO: some examples and documentation on how to build out a cluster
+
+### Generating secrets
+
+Secrets in their very nature are sensitive pieces of data. The ultimate
+storage and injection of these in a production environment needs to be
+carefully considered. For the purposes of this document no specific
+choice has been made but tools like Vault, Sealed Secrets, SOPS, etc
+should be considered. This will only generate the necessary secrets
+using random data to sucessfully continue the installation.
+
+TODO: probably give at least one secure example
+
+```bash
+./scripts/gitops-secrets-gen.sh ./my-k3s.env
+cd /path/to/uc-deploy
+git add secrets/my-k3s
+git commit -m "my-k3s: secrets generation"
+```
+
+### Defining the app deployment
+
+In this section we will use the [App of Apps][app-of-apps] pattern to define
+the deployment of all the components of UnderStack.
+
+```bash
+./scripts/gitops-deploy.sh ./my-k3s.env
+cd /path/to/uc-deploy
+git add clusters/my-k3s
+git commit -m "my-k3s: initial cluster config"
+```
+
+## Final modifications of your deployment
+
+This is point you can make changes to the [ArgoCD][argocd] configs before
+you do the deployment.
+
+## Doing the Deployment
+
+At this point we will use our configs to make the actual deployment.
+Make sure everything you've committed to your deployment repo is pushed
+to your git server so that ArgoCD can access it.
+
+If you do not have ArgoCD deployed then you can use the following:
+
+```bash
+kubectl kustomize --enable-helm \
+ https://github.com/rackerlabs/understack//bootstrap/argocd/ \
+ | kubectl apply -f -
+```
+
+Now configure your ArgoCD to have the credential access to your deploy repo:
+
+```bash
+kubectl -n argocd apply -f /path/to/uc-deploy/secrets/my-k3s/argocd/secret-deploy-repo.yaml
+```
+
+Finally run the following to have ArgoCD deploy the system:
+
+```bash
+kubectl apply -f /path/to/uc-deploy/clusters/my-k3s/app-of-apps.yaml
+```
+
+At this point ArgoCD will work to deploy Understack.
+
+[gitops]:
+[app-of-apps]:
+[argocd]:
diff --git a/mkdocs.yml b/mkdocs.yml
index ed0f68e10..3b70f9aa1 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -53,3 +53,4 @@ nav:
- openstack-helm.md
- secrets.md
- install-understack-ubuntu-k3s.md
+ - gitops-install.md
diff --git a/scripts/gitops-deploy.sh b/scripts/gitops-deploy.sh
new file mode 100755
index 000000000..7225c5b03
--- /dev/null
+++ b/scripts/gitops-deploy.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+usage() {
+ echo "$(basename "$0") " >&2
+ echo "" >&2
+ echo "Generates an initial layout of configs for deploying" >&2
+ exit 1
+}
+
+template() {
+ local subvars
+ subvars="\$DNS_ZONE \$UC_DEPLOY_GIT_URL \$DEPLOY_NAME"
+ cat "$1" | envsubst "${subvars}" > "$2"
+}
+
+if [ $# -ne 1 ]; then
+ usage
+fi
+
+SCRIPTS_DIR="$(dirname "$0")"
+
+if [ ! -f "$1" ]; then
+ echo "Did not get a file with environment variables." >&2
+ usage
+fi
+
+source "$1"
+
+if [ ! -d "${UC_DEPLOY}" ]; then
+ echo "UC_DEPLOY not set to a path." >&2
+ usage
+fi
+
+if [ "x${DEPLOY_NAME}" = "x" ]; then
+ echo "DEPLOY_NAME is not set." >&2
+ usage
+fi
+
+OUTPUT_DIR="${UC_DEPLOY}/clusters/${DEPLOY_NAME}"
+
+export DNS_ZONE
+export UC_DEPLOY_GIT_URL
+export DEPLOY_NAME
+
+for part in operators components; do
+ echo "Creating ${part} configs"
+ mkdir -p "${OUTPUT_DIR}/${part}"
+ for tmpl in $(find "${SCRIPTS_DIR}/../apps/${part}" -type f); do
+ outfile=$(basename "${tmpl}")
+ template "${tmpl}" "${OUTPUT_DIR}/${part}/${outfile}"
+ done
+ rm -rf "${OUTPUT_DIR}/${part}/kustomization.yaml"
+done
+echo "Creating app-of-apps config"
+template "${SCRIPTS_DIR}/../apps/app-of-apps.yaml" "${OUTPUT_DIR}/app-of-apps.yaml"
diff --git a/scripts/gitops-secrets-gen.sh b/scripts/gitops-secrets-gen.sh
new file mode 100755
index 000000000..4923f3f2c
--- /dev/null
+++ b/scripts/gitops-secrets-gen.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+function usage() {
+ echo "$(basename "$0") " >&2
+ echo "" >&2
+ echo "Generates random secrets needed by the apps in this repo" >&2
+ exit 1
+}
+
+if [ $# -ne 1 ]; then
+ usage
+fi
+
+SCRIPTS_DIR=$(dirname "$0")
+
+if [ ! -f "$1" ]; then
+ echo "Did not get a file with environment variables." >&2
+ usage
+fi
+
+source "$1"
+
+if [ ! -d "${UC_DEPLOY}" ]; then
+ echo "UC_DEPLOY not set to a path." >&2
+ usage
+fi
+
+if [ "x${DEPLOY_NAME}" = "x" ]; then
+ echo "DEPLOY_NAME is not set." >&2
+ usage
+fi
+
+if [ "x${UC_DEPLOY_GIT_URL}" = "x" ]; then
+ echo "UC_DEPLOY_GIT_URL is not set." >&2
+ usage
+fi
+
+if [ "x${UC_DEPLOY_SSH_FILE}" = "x" ]; then
+ echo "UC_DEPLOY_SSH_FILE is not set." >&2
+ usage
+fi
+
+if [ ! -f "${UC_DEPLOY_SSH_FILE}" ]; then
+ echo "UC_DEPLOY_SSH_FILE is not a file." >&2
+ usage
+fi
+
+if [ "x${DNS_ZONE}" = "x" ]; then
+ echo "DNS_ZONE is not set." >&2
+ usage
+fi
+
+export DNS_ZONE
+export DEPLOY_NAME
+export SKIP_KUBESEAL=y
+export DO_TMPL_VALUES=y
+mkdir -p "${UC_DEPLOY}/secrets/${DEPLOY_NAME}"
+"${SCRIPTS_DIR}/easy-secrets-gen.sh" "${UC_DEPLOY}/secrets/${DEPLOY_NAME}"
+
+echo "Creating ArgoCD config"
+mkdir -p "${UC_DEPLOY}/secrets/${DEPLOY_NAME}/argocd"
+cat << EOF > "${UC_DEPLOY}/secrets/${DEPLOY_NAME}/argocd/secret-deploy-repo.yaml"
+apiVersion: v1
+kind: Secret
+metadata:
+ name: ${DEPLOY_NAME}-repo
+ labels:
+ argocd.argoproj.io/secret-type: repo-creds
+data:
+ sshPrivateKey: $(cat "${UC_DEPLOY_SSH_FILE}" | base64)
+ type: $(printf "git" | base64)
+ url: $(printf "${UC_DEPLOY_GIT_URL}" | base64)
+EOF