diff --git a/docs/guides/pom.xml b/docs/guides/pom.xml
new file mode 100644
index 0000000000..2e43edfcee
--- /dev/null
+++ b/docs/guides/pom.xml
@@ -0,0 +1,88 @@
+
+
+
+ keycloak-docs-parent
+ org.keycloak
+ 16.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+
+ Keycloak Guides
+ keycloak-guides
+ Keycloak Guides
+
+
+
+ org.keycloak
+ keycloak-guides-maven-plugin
+ 16.0.0-SNAPSHOT
+
+
+
+
+
+
+ org.keycloak
+ keycloak-guides-maven-plugin
+
+
+ generate-asciidoc
+
+ keycloak-guide
+
+
+
+
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+ 1.5.5
+
+
+ asciidoc-to-html
+ generate-resources
+
+ process-asciidoc
+
+
+ ${basedir}/target/generated-guides/server
+ index.adoc
+ html5
+ coderay
+
+
+ ./
+ left
+ left
+ font
+ true
+
+ -
+ true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/guides/src/main/server/all-config.adoc b/docs/guides/src/main/server/all-config.adoc
new file mode 100644
index 0000000000..3420f9fa58
--- /dev/null
+++ b/docs/guides/src/main/server/all-config.adoc
@@ -0,0 +1,27 @@
+<#import "/templates/guide.adoc" as template>
+
+<@template.guide
+title="All configuration"
+summary="All the configuration you will ever need and want">
+
+<#list ctx.options.categories as category>
+<#assign categoryOptions=ctx.options.getValues(category)>
+<#if categoryOptions?has_content>
+== ${category.heading}
+|===
+|Key|CLI|ENV|Description|Default|Values
+<#list categoryOptions as option>
+|${option.key}
+|${option.keyCli}
+|${option.keyEnv}
+|${option.description}
+|${option.defaultValue!}
+|${option.expectedValues?join(", ")}
+<#if option?has_next>
+
+#if>
+#list>
+|===
+#if>
+#list>
+@template.guide>
diff --git a/docs/guides/src/main/server/db.adoc b/docs/guides/src/main/server/db.adoc
new file mode 100644
index 0000000000..ccc60bfa03
--- /dev/null
+++ b/docs/guides/src/main/server/db.adoc
@@ -0,0 +1,23 @@
+<#import "/templates/guide.adoc" as tmpl>
+<#import "/templates/kc.adoc" as kc>
+<#import "/templates/options.adoc" as opts>
+
+<@tmpl.guide
+ title="Relational database setup"
+ summary="Understand how to configure different relational databases for Keycloak"
+ includedOptions="db db.* hostname">
+
+First step is to decide which database vendor you are going to use. Keycloak has support for a number of different vendors.
+
+Selecting the database vendor is done at build-time rather than at runtime. To select the database vendor run:
+
+<@kc.build parameters="--db "/>
+
+Valid options for database vendors include:
+
+<@opts.expectedValues option="db"/>
+
+Once configured you can easily connect to the database with:
+
+<@kc.start parameters="--db-host --db-schema --db-user --db-password "/>
+@tmpl.guide>
\ No newline at end of file
diff --git a/docs/guides/src/main/server/index.adoc b/docs/guides/src/main/server/index.adoc
new file mode 100644
index 0000000000..cf8571a861
--- /dev/null
+++ b/docs/guides/src/main/server/index.adoc
@@ -0,0 +1,5 @@
+= Keycloak server guide
+
+<#list ctx.serverGuides as guide>
+include::${guide}[leveloffset=+1]
+#list>
diff --git a/docs/guides/src/main/templates/guide.adoc b/docs/guides/src/main/templates/guide.adoc
new file mode 100644
index 0000000000..ced8c9e9aa
--- /dev/null
+++ b/docs/guides/src/main/templates/guide.adoc
@@ -0,0 +1,30 @@
+<#macro guide title summary includedOptions="">
+:title: ${title}
+:summary: ${summary}
+
+[[${ctx.getAnchor(title)}]]
+= {title}
+
+{summary}
+
+<#nested>
+
+<#if includedOptions?has_content>
+== Relevant options
+
+|===
+|Key|CLI|ENV|Description|Default|Values
+<#list ctx.options.getOptions(includedOptions) as option>
+|${option.key}
+|${option.keyCli}
+|${option.keyEnv}
+|${option.description}
+|${option.defaultValue!}
+|${option.expectedValues?join(", ")}
+<#if option?has_next>
+
+#if>
+#list>
+|===
+#if>
+#macro>
\ No newline at end of file
diff --git a/docs/guides/src/main/templates/kc.adoc b/docs/guides/src/main/templates/kc.adoc
new file mode 100644
index 0000000000..7b905d2d92
--- /dev/null
+++ b/docs/guides/src/main/templates/kc.adoc
@@ -0,0 +1,13 @@
+<#macro build parameters>
+[source,bash]
+----
+bin/kc.[sh|bat] build ${parameters}
+----
+#macro>
+
+<#macro start parameters>
+[source,bash]
+----
+bin/kc.[sh|bat] start ${parameters}
+----
+#macro>
\ No newline at end of file
diff --git a/docs/guides/src/main/templates/options.adoc b/docs/guides/src/main/templates/options.adoc
new file mode 100644
index 0000000000..e14a8f6eb8
--- /dev/null
+++ b/docs/guides/src/main/templates/options.adoc
@@ -0,0 +1,5 @@
+<#macro expectedValues option>
+<#list ctx.options.getOption(option).expectedValues as expectedValue>
+* ${expectedValue}
+#list>
+#macro>
\ No newline at end of file
diff --git a/docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java b/docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java
new file mode 100644
index 0000000000..108c2a2c65
--- /dev/null
+++ b/docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java
@@ -0,0 +1,22 @@
+package org.keycloak.guides;
+
+import freemarker.template.TemplateException;
+import org.keycloak.guides.maven.GuideBuilder;
+
+import java.io.File;
+import java.io.IOException;
+
+public class DocsBuildDebugUtil {
+
+ public static void main(String[] args) throws IOException, TemplateException {
+ String userDir = System.getProperty("user.dir");
+ File usrDir = new File(System.getProperty("user.dir"));
+ File srcDir = usrDir.toPath().resolve("docs/guides/src/main").toFile();
+ File targetDir = usrDir.toPath().resolve("target/generated-guides-tests").toFile();
+ targetDir.mkdirs();
+ GuideBuilder builder = new GuideBuilder(srcDir, targetDir, null);
+ builder.server();
+ System.out.println("Guides generated to: " + targetDir.getAbsolutePath().toString());
+ }
+
+}
diff --git a/docs/maven-plugin/pom.xml b/docs/maven-plugin/pom.xml
new file mode 100644
index 0000000000..1cc8897bbd
--- /dev/null
+++ b/docs/maven-plugin/pom.xml
@@ -0,0 +1,86 @@
+
+
+
+
+ keycloak-docs-parent
+ org.keycloak
+ 16.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+
+ Keycloak Guides Maven Plugin
+ keycloak-guides-maven-plugin
+ Keycloak Guides Maven Plugin
+ maven-plugin
+
+
+
+
+ io.quarkus
+ quarkus-bom
+ ${quarkus.version}
+ pom
+ import
+
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ 3.6.3
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ 3.6.0
+ provided
+
+
+ org.apache.maven
+ maven-project
+ 2.2.1
+
+
+ org.keycloak
+ keycloak-quarkus-server
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-core
+
+
+ io.smallrye.config
+ smallrye-config
+
+
+ org.freemarker
+ freemarker
+
+
+
+
\ No newline at end of file
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java
new file mode 100644
index 0000000000..9b3b248ca3
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java
@@ -0,0 +1,29 @@
+package org.keycloak.guides.maven;
+
+import java.io.File;
+
+public class Context {
+
+ private File srcDir;
+ private Options options;
+ private String[] serverGuides;
+
+ public Context(File srcDir) {
+ this.srcDir = srcDir;
+ this.options = new Options();
+ this.serverGuides = new File(srcDir, "server").list((dir, f) -> f.endsWith(".adoc") && !f.equals("index.adoc"));
+ }
+
+ public String getAnchor(String title) {
+ return title.toLowerCase().replace(' ', '_');
+ }
+
+ public Options getOptions() {
+ return options;
+ }
+
+ public String[] getServerGuides() {
+ return new File(srcDir, "server").list((dir, f) -> f.endsWith(".adoc") && !f.equals("index.adoc"));
+ }
+
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java
new file mode 100644
index 0000000000..25e127a298
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java
@@ -0,0 +1,44 @@
+package org.keycloak.guides.maven;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+public class FreeMarker {
+
+ private File targetDir;
+ private Map attributes;
+ private Configuration configuration;
+
+ public FreeMarker(File srcDir, File targetDir, Map attributes) throws IOException {
+ this.targetDir = targetDir;
+ this.attributes = attributes;
+
+ configuration = new Configuration(Configuration.VERSION_2_3_31);
+ configuration.setDirectoryForTemplateLoading(srcDir);
+ configuration.setDefaultEncoding("UTF-8");
+ configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+ configuration.setLogTemplateExceptions(false);
+ }
+
+ public void template(String template) throws IOException, TemplateException {
+ Template t = configuration.getTemplate(template);
+ File out = targetDir.toPath().resolve(template).toFile();
+
+ File parent = out.getParentFile();
+ if (!parent.isDirectory()) {
+ parent.mkdir();
+ }
+
+ Writer w = new FileWriter(out);
+ t.process(attributes, w);
+ }
+
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java
new file mode 100644
index 0000000000..3f11967dbf
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java
@@ -0,0 +1,43 @@
+package org.keycloak.guides.maven;
+
+import freemarker.template.TemplateException;
+import org.apache.maven.plugin.logging.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class GuideBuilder {
+
+ private final FreeMarker freeMarker;
+ private final File srcDir;
+ private final File targetDir;
+ private final Log log;
+
+ public GuideBuilder(File srcDir, File targetDir, Log log) throws IOException {
+ this.srcDir = srcDir;
+ this.targetDir = targetDir;
+ this.log = log;
+
+ Map globalAttributes = new HashMap<>();
+ globalAttributes.put("ctx", new Context(srcDir));
+
+ this.freeMarker = new FreeMarker(srcDir, targetDir, globalAttributes);
+ }
+
+ public void server() throws TemplateException, IOException {
+ File serverGuidesDir = new File(srcDir, "server");
+ if (!serverGuidesDir.isDirectory()) {
+ serverGuidesDir.mkdir();
+ }
+
+ for (String t : serverGuidesDir.list((dir, name) -> name.endsWith(".adoc"))) {
+ freeMarker.template("server/" + t);
+ if (log != null) {
+ log.info("Templated: server/" + t);
+ }
+ }
+ }
+
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java
new file mode 100644
index 0000000000..649fd6effc
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java
@@ -0,0 +1,43 @@
+package org.keycloak.guides.maven;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import java.io.File;
+
+@Mojo(name = "keycloak-guide", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
+public class GuideMojo extends AbstractMojo {
+
+ @Parameter(property = "project.build.sourceDirectory")
+ private String sourceDir;
+
+ @Parameter(property = "project.build.directory")
+ private String targetDir;
+
+ @Override
+ public void execute() throws MojoFailureException {
+ try {
+ Log log = getLog();
+ File srcDir = new File(sourceDir).getParentFile();
+ File targetDir = new File(this.targetDir, "generated-guides");
+ if (!targetDir.isDirectory()) {
+ targetDir.mkdirs();
+ }
+
+ log.info("Guide dir: " + srcDir.getAbsolutePath());
+ log.info("Target dir: " + targetDir.getAbsolutePath());
+
+ GuideBuilder g = new GuideBuilder(srcDir, targetDir, log);
+ g.server();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new MojoFailureException("Failed to generated asciidoc files", e);
+ }
+ }
+
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java
new file mode 100644
index 0000000000..34d3524530
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java
@@ -0,0 +1,91 @@
+package org.keycloak.guides.maven;
+
+import org.keycloak.quarkus.runtime.configuration.mappers.ConfigCategory;
+import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+public class Options {
+
+ private final Map options;
+
+ public Options() {
+ options = PropertyMappers.getMappers().stream()
+ .filter(m -> !m.isHidden())
+ .map(m -> new Option(m.getFrom(), m.getCategory(), m.isBuildTime(), m.getDescription(), m.getDefaultValue(), m.getExpectedValues()))
+ .collect(Collectors.toMap(Option::getKey, o -> o, (o1, o2) -> o1)); // Need to ignore duplicate keys??
+ }
+
+ public ConfigCategory[] getCategories() {
+ return ConfigCategory.values();
+ }
+
+ public Collection