adding documentation and container mojos + small fixes
diff --git a/.gitignore b/.gitignore
index 5988948..14aca17 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,11 @@
+# Intellij Idea
 .idea
 *.iws
 *.iml
 *.ipr
+
+# Maven
 target
+
+# scm
+.site-content
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..2b5e164
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,30 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+////
+= Apache Geronimo Arthur
+
+A thin layer on top of Orace GraalVM to build native binaries from your Java softwares.
+
+More on our website: https://geronimo.apache.org/arthur/.
+
+== Build the project
+
+[source,bash]
+----
+mvn clean install
+----
+
+TIP: for now the project uses lombok, ensure it is installed in your IDE if you need it.
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
index bc9c0b3..884f27c 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/ConfigurationGenerator.java
@@ -61,7 +61,7 @@
 
     @Override
     public void run() {
-        final DefautContext context = new DefautContext(classFinder);
+        final DefautContext context = new DefautContext(configuration, classFinder);
         for (final ArthurExtension extension : extensions) {
             log.debug("Executing {}", extension);
             context.setModified(false);
@@ -86,6 +86,7 @@
                     json, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
                 jsonSerializer.accept(context.getReflections(), writer);
             }
+            context.addReflectionConfigFile(json.toAbsolutePath().toString());
         }
         if (!context.getResources().isEmpty() || !context.getBundles().isEmpty()) {
             final ResourcesModel resourcesModel = new ResourcesModel();
@@ -102,6 +103,7 @@
                     json, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
                 jsonSerializer.accept(resourcesModel, writer);
             }
+            context.addResourcesConfigFile(json.toAbsolutePath().toString());
         }
         if (!context.getDynamicProxyModels().isEmpty()) {
             final Set<Collection<String>> proxies = context.getDynamicProxyModels().stream()
@@ -115,6 +117,7 @@
                     json, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
                 jsonSerializer.accept(proxies, writer);
             }
+            context.addDynamicProxiesConfigFile(json.toAbsolutePath().toString());
         }
     }
 
diff --git a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
index d2f68d4..a7f45c8 100644
--- a/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
+++ b/arthur-impl/src/main/java/org/apache/geronimo/arthur/impl/nativeimage/generator/DefautContext.java
@@ -17,11 +17,13 @@
 package org.apache.geronimo.arthur.impl.nativeimage.generator;
 
 import java.lang.annotation.Annotation;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.function.Function;
 
+import org.apache.geronimo.arthur.impl.nativeimage.ArthurNativeImageConfiguration;
 import org.apache.geronimo.arthur.spi.ArthurExtension;
 import org.apache.geronimo.arthur.spi.model.ClassReflectionModel;
 import org.apache.geronimo.arthur.spi.model.DynamicProxyModel;
@@ -32,6 +34,7 @@
 
 @Data
 public class DefautContext implements ArthurExtension.Context {
+    private final ArthurNativeImageConfiguration configuration;
     private final Function<Class<? extends Annotation>, Collection<Class<?>>> finder;
     private final Collection<ClassReflectionModel> reflections = new HashSet<>();
     private final Collection<ResourceModel> resources = new HashSet<>();
@@ -70,4 +73,25 @@
             modified = true;
         }
     }
+
+    public void addReflectionConfigFile(final String path) {
+        if (configuration.getReflectionConfigurationFiles() == null) {
+            configuration.setReflectionConfigurationFiles(new ArrayList<>());
+        }
+        configuration.getReflectionConfigurationFiles().add(path);
+    }
+
+    public void addResourcesConfigFile(final String path) {
+        if (configuration.getResourcesConfigurationFiles() == null) {
+            configuration.setResourcesConfigurationFiles(new ArrayList<>());
+        }
+        configuration.getResourcesConfigurationFiles().add(path);
+    }
+
+    public void addDynamicProxiesConfigFile(final String path) {
+        if (configuration.getDynamicProxyConfigurationFiles() == null) {
+            configuration.setDynamicProxyConfigurationFiles(new ArrayList<>());
+        }
+        configuration.getDynamicProxyConfigurationFiles().add(path);
+    }
 }
diff --git a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java
index bfcdad5..5088436 100644
--- a/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java
+++ b/arthur-impl/src/test/java/org/apache/geronimo/arthur/impl/nativeimage/generator/extension/AnnotationExtensionTest.java
@@ -34,6 +34,7 @@
 import org.apache.geronimo.arthur.api.RegisterField;
 import org.apache.geronimo.arthur.api.RegisterMethod;
 import org.apache.geronimo.arthur.api.RegisterResource;
+import org.apache.geronimo.arthur.impl.nativeimage.ArthurNativeImageConfiguration;
 import org.apache.geronimo.arthur.impl.nativeimage.generator.DefautContext;
 import org.apache.geronimo.arthur.spi.model.ClassReflectionModel;
 import org.apache.geronimo.arthur.spi.model.ResourceBundleModel;
@@ -47,7 +48,7 @@
     void scan() throws Exception {
         final ClassesArchive archive = new ClassesArchive(AnnotationExtensionTest.class.getClasses());
         final AnnotationFinder finder = new AnnotationFinder(archive);
-        final DefautContext context = new DefautContext(finder::findAnnotatedClasses);
+        final DefautContext context = new DefautContext(new ArthurNativeImageConfiguration(), finder::findAnnotatedClasses);
         new AnnotationExtension().execute(context);
         try (final Jsonb jsonb = JsonbBuilder.create(
                 new JsonbConfig().withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL))) {
diff --git a/arthur-maven-plugin/pom.xml b/arthur-maven-plugin/pom.xml
index c18e35b..3e54530 100644
--- a/arthur-maven-plugin/pom.xml
+++ b/arthur-maven-plugin/pom.xml
@@ -69,6 +69,17 @@
     </dependency>
 
     <dependency>
+      <groupId>com.google.cloud.tools</groupId>
+      <artifactId>jib-core</artifactId>
+      <version>0.12.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <version>28.0-jre</version>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.xbean</groupId>
       <artifactId>xbean-finder-shaded</artifactId>
     </dependency>
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java
new file mode 100644
index 0000000..513d87e
--- /dev/null
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ArthurMojo.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */package org.apache.geronimo.arthur.maven.mojo;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+
+public abstract class ArthurMojo extends AbstractMojo {
+    /**
+     * Once built, the binary path is set in maven properties.
+     * This enables to configure the prefix to use.
+     */
+    @Parameter(defaultValue = "arthur.", property = "arthur.propertiesPrefix")
+    protected String propertiesPrefix;
+
+    @Parameter(defaultValue = "${project}", readonly = true)
+    protected MavenProject project;
+
+    @Parameter(defaultValue = "${settings.offline}", readonly = true)
+    protected boolean offline;
+}
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/DockerMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/DockerMojo.java
new file mode 100644
index 0000000..cca81f0
--- /dev/null
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/DockerMojo.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */package org.apache.geronimo.arthur.maven.mojo;
+
+import com.google.cloud.tools.jib.api.Containerizer;
+import com.google.cloud.tools.jib.api.DockerDaemonImage;
+import com.google.cloud.tools.jib.api.InvalidImageReferenceException;
+import org.apache.maven.plugins.annotations.Mojo;
+
+/**
+ * Alternate mojo to jib:dockerBuild to avoid to bundle useless files.
+ * Can be replaced by vanilla jib when it will support it, see https://github.com/GoogleContainerTools/jib/issues/1857
+ */
+@Mojo(name = "docker", threadSafe = true)
+public class DockerMojo extends JibMojo {
+    @Override
+    protected Containerizer createContainer() throws InvalidImageReferenceException {
+        return Containerizer.to(DockerDaemonImage.named(this.to));
+    }
+}
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ImageMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ImageMojo.java
new file mode 100644
index 0000000..c1be72b
--- /dev/null
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/ImageMojo.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */package org.apache.geronimo.arthur.maven.mojo;
+
+import static java.util.Optional.ofNullable;
+
+import java.util.Optional;
+
+import com.google.cloud.tools.jib.api.Containerizer;
+import com.google.cloud.tools.jib.api.ImageReference;
+import com.google.cloud.tools.jib.api.InvalidImageReferenceException;
+import com.google.cloud.tools.jib.api.RegistryImage;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
+import org.apache.maven.settings.crypto.SettingsDecrypter;
+import org.apache.maven.settings.crypto.SettingsDecryptionResult;
+
+/**
+ * Alternate mojo to jib:build to avoid to bundle useless files.
+ * Can be replaced by vanilla jib when it will support it, see https://github.com/GoogleContainerTools/jib/issues/1857
+ */
+@Mojo(name = "image", threadSafe = true)
+public class ImageMojo extends JibMojo {
+    /**
+     * Server identifier (in settings.xml) used to authenticate to the remote image registry.
+     */
+    @Parameter(property = "arthur.serverId")
+    private String serverId;
+
+    @Component
+    private SettingsDecrypter settingsDecrypter;
+
+    @Parameter(defaultValue = "${session}", readonly = true)
+    private MavenSession session;
+
+    @Override
+    protected Containerizer createContainer() throws InvalidImageReferenceException {
+        final ImageReference reference = ImageReference.parse(to);
+        final RegistryImage image = RegistryImage.named(reference);
+        registerCredentials(reference, image);
+        return Containerizer.to(image);
+    }
+
+    private void registerCredentials(final ImageReference reference, final RegistryImage registryImage) {
+        ofNullable(serverId)
+            .map(Optional::of)
+            .orElseGet(() -> ofNullable(reference.getRegistry()))
+            .map(id -> session.getSettings().getServer(id))
+            .map(it -> settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(it)))
+            .map(SettingsDecryptionResult::getServer)
+            .ifPresent(server -> registryImage.addCredential(server.getUsername(), server.getPassword()));
+    }
+}
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/JibMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/JibMojo.java
new file mode 100644
index 0000000..27fda59
--- /dev/null
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/JibMojo.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.maven.mojo;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.stream.Collectors.toList;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import com.google.cloud.tools.jib.api.AbsoluteUnixPath;
+import com.google.cloud.tools.jib.api.CacheDirectoryCreationException;
+import com.google.cloud.tools.jib.api.Containerizer;
+import com.google.cloud.tools.jib.api.FilePermissions;
+import com.google.cloud.tools.jib.api.ImageReference;
+import com.google.cloud.tools.jib.api.InvalidImageReferenceException;
+import com.google.cloud.tools.jib.api.Jib;
+import com.google.cloud.tools.jib.api.JibContainer;
+import com.google.cloud.tools.jib.api.JibContainerBuilder;
+import com.google.cloud.tools.jib.api.LayerConfiguration;
+import com.google.cloud.tools.jib.api.LayerEntry;
+import com.google.cloud.tools.jib.api.LogEvent;
+import com.google.cloud.tools.jib.api.Ports;
+import com.google.cloud.tools.jib.api.RegistryException;
+import org.apache.maven.plugins.annotations.Parameter;
+
+public abstract class JibMojo extends ArthurMojo {
+    /**
+     * Base image to use. Scratch will ensure it starts from an empty image.
+     */
+    @Parameter(property = "arthur.from", defaultValue = "scratch")
+    private String from;
+
+    /**
+     * Ports to expose.
+     */
+    @Parameter(property = "arthur.ports")
+    private List<String> ports;
+
+    /**
+     * Other files to include in the image, note that their permissions will not be executable.
+     */
+    @Parameter(property = "arthur.files")
+    private List<File> otherFiles;
+
+    /**
+     * Program arguments.
+     */
+    @Parameter(property = "arthur.programArguments")
+    private List<String> programArguments;
+
+    /**
+     * Image environment.
+     */
+    @Parameter(property = "arthur.environment")
+    private Map<String, String> environment;
+
+    /**
+     * Image labels.
+     */
+    @Parameter(property = "arthur.labels")
+    private Map<String, String> labels;
+
+    /**
+     * Timestamp creation for the image, it is recommended to set it fixed for reproducibility.
+     */
+    @Parameter(property = "arthur.creationTimestamp", defaultValue = "1")
+    private long creationTimestamp;
+
+    /**
+     * Entry point to use.
+     */
+    @Parameter(property = "arthur.entrypoint", defaultValue = "/${project.artifactId}")
+    private String entrypoint;
+
+    /**
+     * Where is the binary to include. It defaults on native-image output if done before in the same execution
+     */
+    @Parameter(property = "arthur.binarySource")
+    private File binarySource;
+
+    /**
+     * Should base images be cached.
+     */
+    @Parameter(property = "arthur.enableCache", defaultValue = "true")
+    private boolean enableCache;
+
+    /**
+     * Are insecure registries allowed.
+     */
+    @Parameter(property = "arthur.allowInsecureRegistries", defaultValue = "false")
+    private boolean allowInsecureRegistries;
+
+    /**
+     * Where to cache application layers.
+     */
+    @Parameter(property = "arthur.applicationLayersCache", defaultValue = "${project.build.directory}/arthur_jib_cache/application")
+    private File applicationLayersCache;
+
+    /**
+     * Where to cache base layers layers (if any).
+     */
+    @Parameter(property = "arthur.baseLayersCache", defaultValue = "${project.build.directory}/arthur_jib_cache/base")
+    private File baseLayersCache;
+
+    /**
+     * Number of threads used to build.
+     */
+    @Parameter(property = "arthur.threads", defaultValue = "1")
+    private int threads;
+
+    /**
+     * Build timeout in milliseconds if it is using threads > 1.
+     */
+    @Parameter(property = "arthur.timeout", defaultValue = "3600000")
+    private long timeout;
+
+    /**
+     * Target image name.
+     */
+    @Parameter(property = "arthur.to", defaultValue = "${project.artifactId}:${project.version}")
+    protected String to;
+
+    protected abstract Containerizer createContainer() throws InvalidImageReferenceException;
+
+    @Override
+    public void execute() {
+        final JibContainerBuilder prepared = prepare();
+        withExecutor(es -> {
+            try {
+                final Containerizer containerizer = createContainer();
+                final JibContainer container = prepared.containerize(configure(containerizer, es));
+                if (propertiesPrefix != null) {
+                    project.getProperties().setProperty(propertiesPrefix + "image.imageId", container.getImageId().getHash());
+                    project.getProperties().setProperty(propertiesPrefix + "image.digest", container.getDigest().getHash());
+                }
+                getLog().info("Built '" + to + "'");
+            } catch (final InterruptedException e) {
+                Thread.currentThread().interrupt();
+            } catch (final RegistryException | IOException | CacheDirectoryCreationException | ExecutionException | InvalidImageReferenceException e) {
+                throw new IllegalStateException(e);
+            }
+        });
+    }
+
+    private void withExecutor(final Consumer<ExecutorService> consumer) {
+        if (threads > 1) {
+            final ExecutorService executorService = Executors.newFixedThreadPool(threads, new ThreadFactory() {
+                private final AtomicInteger counter = new AtomicInteger();
+
+                @Override
+                public Thread newThread(final Runnable r) {
+                    return new Thread(r, JibMojo.class.getName() + "-" + counter.incrementAndGet());
+                }
+            });
+            try {
+                consumer.accept(executorService);
+            } finally {
+                executorService.shutdown();
+                try {
+                    executorService.awaitTermination(timeout, MILLISECONDS);
+                } catch (final InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+        } else {
+            consumer.accept(null);
+        }
+    }
+
+    private Containerizer configure(final Containerizer to, final ExecutorService executorService) {
+        to.setAlwaysCacheBaseImage(enableCache);
+        to.setAllowInsecureRegistries(allowInsecureRegistries);
+        to.setApplicationLayersCache(applicationLayersCache.toPath());
+        to.setBaseImageLayersCache(baseLayersCache.toPath());
+        to.setOfflineMode(offline);
+        to.setToolName("Arthur " + getClass().getSimpleName().replace("Mojo", ""));
+        to.setExecutorService(executorService);
+        to.addEventHandler(LogEvent.class, event -> {
+            switch (event.getLevel()) {
+                case INFO:
+                case LIFECYCLE:
+                case PROGRESS:
+                    getLog().info(event.getMessage());
+                    break;
+                case WARN:
+                    getLog().warn(event.getMessage());
+                    break;
+                case ERROR:
+                    getLog().error(event.getMessage());
+                    break;
+                case DEBUG:
+                default:
+                    getLog().debug(event.getMessage());
+                    break;
+            }
+        });
+        return to;
+    }
+
+    private JibContainerBuilder prepare() {
+        try {
+            final JibContainerBuilder from = Jib.from(ImageReference.parse(this.from));
+            if (ports != null) {
+                from.setExposedPorts(Ports.parse(ports));
+            }
+            if (environment != null) {
+                from.setEnvironment(environment);
+            }
+            if (labels != null) {
+                from.setLabels(labels);
+            }
+            if (programArguments != null) {
+                from.setProgramArguments(programArguments);
+            }
+            from.setCreationTime(creationTimestamp < 0 ? Instant.now() : Instant.ofEpochMilli(creationTimestamp));
+            from.setEntrypoint(entrypoint);
+
+            final Path source = ofNullable(binarySource)
+                    .map(File::toPath)
+                    .orElseGet(() -> Paths.get(requireNonNull(
+                            project.getProperties().getProperty(propertiesPrefix + "binary.path"),
+                            "No binary path found, ensure to run native-image before or set entrypoint")));
+            from.setLayers(Stream.concat(
+                    otherFiles != null && !otherFiles.isEmpty() ?
+                            Stream.of(createOthersLayer()) :
+                            Stream.empty(),
+                    Stream.of(LayerConfiguration.builder()
+                            .setName("Binary")
+                            .addEntry(new LayerEntry(
+                                    source, AbsoluteUnixPath.get(entrypoint), FilePermissions.fromOctalString("755"),
+                                    getTimestamp(source)))
+                            .build()))
+                    .collect(toList()));
+
+            return from;
+        } catch (final InvalidImageReferenceException | IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private Instant getTimestamp(final Path source) throws IOException {
+        return creationTimestamp < 0 ? Files.getLastModifiedTime(source).toInstant() : Instant.ofEpochMilli(creationTimestamp);
+    }
+
+    private LayerConfiguration createOthersLayer() {
+        final LayerConfiguration.Builder builder = LayerConfiguration.builder().setName("Others");
+        otherFiles.stream().map(File::toPath).forEach(f -> {
+            final AbsoluteUnixPath containerPath = AbsoluteUnixPath.get(project.getBasedir().toPath().relativize(f).toString());
+            if (containerPath.toString().contains("..")) {
+                throw new IllegalArgumentException("You can only include files included in basedir");
+            }
+            try {
+                if (Files.isDirectory(f)) {
+                    builder.addEntryRecursive(
+                            f, containerPath,
+                            (l, c) -> FilePermissions.DEFAULT_FILE_PERMISSIONS,
+                            (l, c) -> {
+                                try {
+                                    return getTimestamp(l);
+                                } catch (final IOException e) {
+                                    throw new IllegalStateException(e);
+                                }
+                            });
+                } else {
+                    builder.addEntry(f, containerPath, FilePermissions.DEFAULT_FILE_PERMISSIONS, getTimestamp(f));
+                }
+            } catch (final IOException e) {
+                throw new IllegalStateException(e);
+            }
+        });
+        return builder.build();
+    }
+}
diff --git a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java
index 4e72b68..c36c2aa 100644
--- a/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java
+++ b/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo/NativeImageMojo.java
@@ -67,8 +67,11 @@
 import org.eclipse.aether.resolution.ArtifactResolutionException;
 import org.eclipse.aether.resolution.ArtifactResult;
 
+/**
+ * Generates a native binary from current project.
+ */
 @Mojo(name = "native-image", defaultPhase = PACKAGE, requiresDependencyResolution = TEST, threadSafe = true)
-public class NativeImageMojo extends AbstractMojo {
+public class NativeImageMojo extends ArthurMojo {
     //
     // ArthurNativeImageConfiguration
     //
@@ -317,21 +320,25 @@
     @Parameter(property = "arthur.supportedTypes", defaultValue = "jar,zip")
     private List<String> supportedTypes;
 
+    /**
+     * Should jar be used instead of exploded folder (target/classes).
+     * Note this option disable the support of module test classes.
+     */
+    @Parameter(property = "project.usePackagedArtifact", defaultValue = "false")
+    private boolean usePackagedArtifact;
+
     @Parameter(defaultValue = "${project.packaging}", readonly = true)
     private String packaging;
 
+    @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.${project.packaging}")
+    private File jar;
+
     @Parameter(defaultValue = "${project.build.outputDirectory}")
     private File classes;
 
     @Parameter(defaultValue = "${project.build.testOutputDirectory}")
     private File testClasses;
 
-    @Parameter(defaultValue = "${settings.offline}", readonly = true)
-    private boolean offline;
-
-    @Parameter(defaultValue = "${project}", readonly = true)
-    private MavenProject project;
-
     @Parameter(defaultValue = "${repositorySystemSession}")
     protected RepositorySystemSession repositorySystemSession;
 
@@ -406,6 +413,10 @@
         } finally {
             thread.setContextClassLoader(oldLoader);
         }
+
+        if (propertiesPrefix != null) {
+            project.getProperties().setProperty(propertiesPrefix + "binary.path", output);
+        }
     }
 
     private String buildCacheGav(final String graalPlatform) {
@@ -433,9 +444,11 @@
 
     private Stream<File> findClasspathFiles() {
         return Stream.concat(Stream.concat(
-                Stream.concat(
-                        Stream.of(classes),
-                        supportTestArtifacts ? Stream.of(testClasses) : Stream.empty()),
+                usePackagedArtifact ?
+                        Stream.of(jar) :
+                        Stream.concat(
+                            Stream.of(classes),
+                            supportTestArtifacts ? Stream.of(testClasses) : Stream.empty()),
                 project.getArtifacts().stream()
                         .filter(a -> !excludedArtifacts.contains(a.getGroupId() + ':' + a.getArtifactId()))
                         .filter(this::handleTestInclusion)
diff --git a/documentation/pom.xml b/documentation/pom.xml
new file mode 100644
index 0000000..2fed8f6
--- /dev/null
+++ b/documentation/pom.xml
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="
+            http://maven.apache.org/POM/4.0.0
+            http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>arthur</artifactId>
+    <groupId>org.apache.geronimo.arthur</groupId>
+    <version>1.0.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>documentation</artifactId>
+  <name>Arthur :: Documentation</name>
+
+  <properties>
+    <geronimo-arthur.shortname>documentation</geronimo-arthur.shortname>
+
+    <doc.mojo.basedir>
+      ${project.parent.basedir}/arthur-maven-plugin/src/main/java/org/apache/geronimo/arthur/maven/mojo
+    </doc.mojo.basedir>
+  </properties>
+
+  <dependencies>
+    <!-- we want to be last in the reactor to be able to rely on relative paths if we generate doc -->
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>arthur-maven-plugin</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <!-- generation dependencies -->
+    <dependency>
+      <groupId>org.tomitribe</groupId>
+      <artifactId>tomitribe-crest</artifactId>
+      <version>0.11-SNAPSHOT</version>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.xbean</groupId>
+          <artifactId>xbean-asm5-shaded</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.apache.xbean</groupId>
+          <artifactId>xbean-finder-shaded</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <version>${slf4j.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.asciidoctor</groupId>
+      <artifactId>asciidoctorj</artifactId>
+      <version>2.1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.asciidoctor</groupId>
+      <artifactId>asciidoctorj-diagram</artifactId>
+      <version>1.5.18</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jruby</groupId>
+      <artifactId>jruby-complete</artifactId>
+      <version>9.2.8.0</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>1.6.0</version>
+        <executions>
+          <execution>
+            <id>default-cli</id>
+            <phase />
+            <goals>
+              <goal>java</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>generate</id>
+            <phase>prepare-package</phase>
+            <goals>
+              <goal>java</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <mainClass>org.apache.geronimo.arthur.documentation.DocumentationGeneratorLauncher</mainClass>
+          <arguments>
+            <argument>generate</argument>
+            <argument>--work-directory=${project.build.directory}/generated_documentation</argument>
+            <argument>--content-location=${project.basedir}/src/content</argument>
+            <argument>--static-location=${project.basedir}/src/static</argument>
+            <argument>--output=${project.build.directory}/documentation</argument>
+            <argument>--template-header=${project.basedir}/src/template/header.html</argument>
+            <argument>--template-footer=${project.basedir}/src/template/footer.html</argument>
+            <argument>--mojo=${doc.mojo.basedir}/DockerMojo.java</argument>
+            <argument>--mojo=${doc.mojo.basedir}/ImageMojo.java</argument>
+            <argument>--mojo=${doc.mojo.basedir}/NativeImageMojo.java</argument>
+          </arguments>
+          <systemProperties>
+            <systemProperty>
+              <key>org.slf4j.simpleLogger.showThreadName</key>
+              <value>false</value>
+            </systemProperty>
+            <systemProperty>
+              <key>org.slf4j.simpleLogger.levelInBrackets</key>
+              <value>true</value>
+            </systemProperty>
+            <systemProperty>
+              <key>org.slf4j.simpleLogger.showShortLogName</key>
+              <value>false</value>
+            </systemProperty>
+          </systemProperties>
+        </configuration>
+      </plugin>
+      <plugin> <!-- "mvn meecrowave:bake" to check out the generated website -->
+        <groupId>org.apache.meecrowave</groupId>
+        <artifactId>meecrowave-maven-plugin</artifactId>
+        <version>1.2.9</version>
+        <configuration>
+          <webapp>${project.build.directory}/documentation</webapp>
+          <webResourceCached>true</webResourceCached>
+          <reloadGoals>prepare-package</reloadGoals> <!-- mvn meecrowave:bake -DskipTests, then "r" to reload -->
+          <scanningExcludes>a,b,c,d,i,j,m,n,o,t</scanningExcludes>
+        </configuration>
+      </plugin>
+      <plugin> <!-- "mvn clean prepare-package scm-publish:publish-scm -DskipTests" to publish the website -->
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-scm-publish-plugin</artifactId>
+        <version>1.0-beta-2</version>
+        <executions>
+          <execution>
+            <id>scm-publish</id>
+            <phase>site-deploy</phase>
+            <goals>
+              <goal>publish-scm</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <content>${project.build.directory}/documentation</content>
+          <pubScmUrl>scm:svn:https://svn.apache.org/repos/infra/websites/production/geronimo/content/arthur</pubScmUrl>
+          <tryUpdate>true</tryUpdate>
+          <checkoutDirectory>${project.parent.basedir}/.site-content</checkoutDirectory>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <distributionManagement>
+    <repository>
+      <id>localhost</id>
+      <url>file://${project.basedir}/target/deploy/</url>
+    </repository>
+    <snapshotRepository>
+      <id>localhost</id>
+      <url>file://${project.basedir}/target/deploy/</url>
+    </snapshotRepository>
+  </distributionManagement>
+</project>
diff --git a/documentation/src/content/api.adoc b/documentation/src/content/api.adoc
new file mode 100644
index 0000000..0ba0bed
--- /dev/null
+++ b/documentation/src/content/api.adoc
@@ -0,0 +1,65 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+////
+= Arthur API
+
+Arthur API enables you to mark in your code some Graal metainformation.
+Under the hood it just uses a meta-extension linked to this specific API.
+
+For more information about extensions, see link:spi.html[SPI] documentation.
+
+== Dependencies
+
+[source,xml]
+----
+<dependency>
+  <groupId>${project.groupId}</groupId>
+  <artifactId>arthur-api</artifactId>
+  <version>${project.version}</version>
+</dependency>
+----
+
+== Usage
+
+`@Register*` annotation enables to register reflection and resources on your classes directly.
+
+Example:
+
+[source,java]
+----
+@RegisterClass(all = true) <1>
+public class All {
+    private String name;
+
+    public All() {
+        // no-op
+    }
+
+    public All(final String name) {
+        this.name = name;
+    }
+
+    public boolean isAll() {
+        return true;
+    }
+}
+----
+
+<1> will register the public constructors, fields and methods as available in the native image.
+
+---
+
+Previous: link:documentation.html[Documentation] Next: link:spi.html[Arthur SPI]
diff --git a/documentation/src/content/community.adoc b/documentation/src/content/community.adoc
new file mode 100644
index 0000000..1a9120d
--- /dev/null
+++ b/documentation/src/content/community.adoc
@@ -0,0 +1,35 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+////
+= Community
+
+=== The Source Code
+
+Source code is available on link:https://gitbox.apache.org/repos/asf?p=geronimo-arthur.git[gitbox].
+
+=== Social Media: cause contributing is not only about coding
+
+- link:https://twitter.com/ASFGeronimo[icon:twitter[] Twitter]
+- link:https://webchat.freenode.net/?channels=geronimo[icon:terminal[] IRC]
+
+=== Mailing lists
+
+* link:mailto:user-subscribe@geronimo.apache.org[Subscribe] to user@geronimo.apache.org
+* link:mailto:user-unsubscribe@geronimo.apache.org[Unsubscribe] from user@geronimo.apache.org
+* link:mailto:user@geronimo.apache.org[Post a message] on user@geronimo.apache.org
+
+Archives can be found on link:http://apache-geronimo.328035.n3.nabble.com/[Nabble].
+
diff --git a/documentation/src/content/documentation.adoc b/documentation/src/content/documentation.adoc
new file mode 100644
index 0000000..c2af88f
--- /dev/null
+++ b/documentation/src/content/documentation.adoc
@@ -0,0 +1,28 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+////
+= Arthur Documentation
+
+Arthur project is composed of multiple module:
+
+- link:api.html[API]: it contains some end user API integrated with built-in extensions to simplify application graal-ification,
+- link:spi.html[SPI]: it contains the extension API intended to be used by libraries or integrators to simplify the graal-ification of a coding pattern or framework coding style,
+- link:implementation.html[Implementation]: it does the orchestration of the extensions execution before the `native-image` command line generation and execution,
+- link:maven.html[Maven Plugin]: it wraps the Implementation in a Maven Plugin and provides Docker helper mojos.
+
+---
+
+Previous: link:index.html[Index] Next: link:api.html[Arthur API]
diff --git a/documentation/src/content/downloads.adoc b/documentation/src/content/downloads.adoc
new file mode 100644
index 0000000..2e9ce6e
--- /dev/null
+++ b/documentation/src/content/downloads.adoc
@@ -0,0 +1,20 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+////
+= Arthur Downloads
+
+Arthur is available on Apache link:https://repository.apache.org/content/repositories/snapshots[snapshots] repository.
+Once released it will be available on central.
diff --git a/documentation/src/content/implementation.adoc b/documentation/src/content/implementation.adoc
new file mode 100644
index 0000000..6529f14
--- /dev/null
+++ b/documentation/src/content/implementation.adoc
@@ -0,0 +1,82 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+////
+= Arthur Implementation
+
+Arthur implementation provides `org.apache.geronimo.arthur.impl.nativeimage.ArthurNativeImageExecutor` which executes the extensions and run `native-image`.
+
+== Dependencies
+
+[source,xml]
+----
+<dependency>
+  <groupId>${project.groupId}</groupId>
+  <artifactId>arthur-impl</artifactId>
+  <version>${project.version}</version>
+</dependency>
+----
+
+== Usage
+
+By itself implementation module will miss a class finder and a JSON serializer.
+You will likely want to add these dependencies to be able to use it in standalone mode - maven plugin does it for you:
+
+[source,xml]
+----
+<dependency>
+  <groupId>org.apache.xbean</groupId>
+  <artifactId>xbean-finder-shaded</artifactId>
+</dependency>
+<dependency>
+  <groupId>org.apache.geronimo.specs</groupId>
+  <artifactId>geronimo-json_1.1_spec</artifactId>
+</dependency>
+<dependency>
+  <groupId>org.apache.geronimo.specs</groupId>
+  <artifactId>geronimo-jsonb_1.0_spec</artifactId>
+</dependency>
+<dependency>
+  <groupId>org.apache.johnzon</groupId>
+  <artifactId>johnzon-jsonb</artifactId>
+</dependency>
+----
+
+Once you have that you can create a `Jsonb` instance to serialize the model and an `AnnotationFinder` to let `Context#finder` be implemented:
+
+[source,java]
+----
+try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+             .setProperty("johnzon.cdi.activated", false)
+             .withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL))) {
+    final AnnotationFinder finder = new AnnotationFinder(createScannedArchive());
+    new ArthurNativeImageExecutor(
+        ArthurNativeImageExecutor.ExecutorConfiguration.builder()
+                .jsonSerializer(jsonb::toJson)
+                .finder(finder::findAnnotatedClasses)
+                .configuration(configuration)
+                .workingDirectory(workdir.toPath().resolve("generated_configuration"))
+                .build())
+        .run();
+} catch (final Exception e) {
+    throw new IllegalStateException(e);
+} finally {
+    thread.setContextClassLoader(oldLoader);
+}
+----
+
+---
+
+Previous: link:spi.html[Arthur SPI] Next: link:maven.html[Arthur Maven Plugin]
diff --git a/documentation/src/content/index.adoc b/documentation/src/content/index.adoc
new file mode 100644
index 0000000..06d51c0
--- /dev/null
+++ b/documentation/src/content/index.adoc
@@ -0,0 +1,23 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+////
+= Apache Geronimo Arthur
+
+== GraalVM for everyone
+
+Apache Geronimo Arthur is an effort to buil a thin layer on top of Oracle GraalVM to build native binaries from your Java programs.
+
+Learn more on our link:documentation.html[documentation].
diff --git a/documentation/src/content/maven.adoc b/documentation/src/content/maven.adoc
new file mode 100644
index 0000000..0950220
--- /dev/null
+++ b/documentation/src/content/maven.adoc
@@ -0,0 +1,315 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+////
+= Apache Geronimo Arthur Maven
+
+Arthur Maven plugin provides a way to generate `native-image` configuration and execute it.
+It also enables to automatically download GraalVM avoiding you to manage the installation part.
+
+== Get started in 5 minutes
+
+First thing to do to enable a native build of your application is to add the plugin with your application entry point (`main(String[])`):
+
+[source,xml]
+----
+<plugin>
+    <groupId>org.apache.geronimo.arthur</groupId>
+    <artifactId>arthur-maven-plugin</artifactId>
+    <version>${arthur.version}</version>
+    <configuration>
+        <main>org.kamelot.Florida</main> <1>
+    </configuration>
+</plugin>
+----
+
+<1> The application to compile natively
+
+Once it is done, you can run `mvn [process-classes] arthur:native-image`.
+
+== Graal Setup
+
+You probably notices that in previous part we didn't mention you need to set your `JAVA_HOME` to a Graal instance or so.
+This is because the plugin is able to download GraalVM if it is not already done and to install - using GraalVM `gu` tool - `native-image`extension.
+You will find all the details of that feature in the configuration section but it is important to note a few points:
+
+1. You can explicit the `native-image` instance to use and avoid the implicit installation setting the configuration `nativeImage`,
+2. GraalVM version is configurable (note that it relies on SDKMan by default so ensure the last version you want to upgrade immediately is available),
+3. The plugin caches the GraalVM archive and its unpack flavor in your local maven repository to avoid to download and explode it each time.
+
+== Execution example
+
+Here is a dump of a sample execution:
+
+[source]
+----
+$ mvn arthur:native-image -o
+[INFO] Scanning for projects...
+[INFO] 
+[INFO] ---------------------< org.apache.geronimo.arthur:sample >---------------------
+[INFO] Building Arthur :: Sample 1.0.0-SNAPSHOT
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO] 
+[INFO] --- arthur-maven-plugin:1.0.0-SNAPSHOT:native-image (default-cli) @ jdbc ---
+[INFO] Using GRAAL: /home/rmannibucau/.m2/repository/org/apache/geronimo/arthur/cache/graal/19.2.1/distribution_exploded <1>
+[INFO] Extension org.apache.geronimo.arthur.maven.extension.MavenArthurExtension updated build context <2>
+[INFO] Creating resources model '/opt/dev/arthur/sample/target/arthur_workdir/generated_configuration/resources.arthur.json' <3>
+[INFO] Creating dynamic proxy model '/opt/dev/arthur/sample/target/arthur_workdir/generated_configuration/dynamicproxies.arthur.json' <3>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]    classlist:   6,427.14 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]        (cap):   2,126.93 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]        setup:   3,480.51 ms <4>
+ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider com.oracle.truffle.js.scriptengine.GraalJSEngineFactory could not be instantiated
+ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider com.oracle.truffle.js.scriptengine.GraalJSEngineFactory could not be instantiated
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]   (typeflow):  15,605.58 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]    (objects):  11,369.56 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]   (features):   1,901.64 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]     analysis:  30,057.86 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]     (clinit):     568.59 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]     universe:   1,196.02 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]      (parse):   2,334.15 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]     (inline):   3,093.09 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]    (compile):  20,410.16 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]      compile:  27,352.51 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]        image:   2,792.40 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]        write:     677.26 ms <4>
+/opt/dev/arthur/sample/target/sample.graal.bin:16697]      [total]:  72,280.13 ms <4>
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time:  01:14 min
+[INFO] Finished at: 2019-10-29T17:55:16+01:00
+[INFO] ------------------------------------------------------------------------
+
+----
+
+<1> The GraalVM distribution was already existing so was not download and was directly used,
+<2> Maven inline Graal configuration (resources, reflection, bundles) was set up,
+<3> Dynamic (Arthur prebuild phase) configuration was dumped before launching `native-image`,
+<4> `native-image` execution/phases
+
+== Native Image Mojo configuration
+
+The plugin is quite configurable and even enable to build a main in a test scope.
+Here is the full list of available configuration.
+
+include::{generated_dir}/generated_nativeimage_mojo.adoc[]
+
+== What about docker?
+
+One of the main reasons to go native is to reduce the startup latency and the memory consumption.
+This is literally a paraphrase to say we want to run into Kubernetes.
+Therefore the question to bundle the native binary as a docker image comes pretty quickly.
+
+=== Sample Dockerfile
+
+There are multiple option to create a docker image but one interesting point to mention is to build the binary in a docker image to ensure the target binary matches the target image architecture.
+For that case, we recommend you to build your project as you want, copy the project in a builder docker image and use a multi-stage builder to ensure you build on the platform you will run (the `FROM` of your `Dockerfile`).
+
+TIP: to avoid a lot of downloads/latency it can be neat to *not* bind `arthur:native-image` plugin goal to any phase and just call it explicitly in your build. To do that just use `<phase />` instead of an explicit phase if you define a custom execution.
+
+Here is an example:
+
+[source,Dockerfile]
+----
+FROM maven:3.6-jdk-8-slim AS builder
+COPY . /project
+COPY target/m2 /root/.m2/repository
+WORKDIR /project
+RUN mvn package arthur:native-image
+
+FROM scratch
+COPY --from=builder /project/target/myapp.graal.bin /project/myapp.graal.bin
+ENTRYPOINT [ "/project/myapp.graal.bin" ]
+----
+
+To avoid to download all needed dependencies all the time don't forget to prepare a local copy of the maven repository, here is how to launch this docker creation assuming you put this `Dockerfile` at the root of your maven project:
+
+[source,sh]
+----
+# prepare the local copy of the m2 with dependency plugin (for examples, some other plugins will also include build maven plugins in the dedicated m2)
+mvn dependency:copy-dependencies -Dmdep.useRepositoryLayout=true -DoutputDirectory=target/m2
+
+# actually build
+docker build -t sample:latest
+----
+
+TIP: you can also do the package on your machine and skip it in the docker build but the tip to prepare a local m2 copy is still helping to speed up the build.
+
+TIP: if you use private repositories, don't forget to copy your settings.xml as well (in `/root/.m2` for `maven`/builder image).
+
+IMPORTANT: when building on a jenkins running on Kubernetes, ensure to use a build image with an architecture compatible, this avoids all that setup and you can just run it directly as if it was locally.
+
+=== Jib to create an image
+
+Jib is an awesome project propulsed by Google enabling to build docker images without docker.
+It can be used to put your binary into a very light image (based on the "empty" `scratch` one). Here is how to add it to your pom:
+
+[source,xml]
+----
+<plugin> <1>
+    <groupId>com.google.cloud.tools</groupId>
+    <artifactId>jib-maven-plugin</artifactId>
+    <version>1.7.0</version>
+    <configuration>
+        <from>
+            <image>scratch</image> <2>
+        </from>
+        <to>
+            <image>sample:latest</image>
+        </to>
+        <extraDirectories>
+            <paths>
+                <path>${project.build.directory}/graal</path> <3>
+            </paths>
+            <permissions>
+                <permission> <4>
+                    <file>/sample.graal.bin</file>
+                    <mode>755</mode>
+                </permission>
+            </permissions>
+        </extraDirectories>
+        <container>
+            <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
+            <entrypoint>
+                <arg>/sample.graal.bin</arg> <5>
+            </entrypoint>
+        </container>
+    </configuration>
+</plugin>
+----
+
+<1> Add `jib-maven-plugin` in your `pom.xml`
+<2> Use `scratch` keyword as base image (which almost means "nothing" or "empty image")
+<3> Reference the folder containing your binary (you can desire to tune the `output`parameter of the `arthur-maven-plugin` to ensure the folder only contains your binary since the full folder will be added to the image)
+<4> Ensure the binary has the right permission (executable)
+<5> Set the binary as entrypoint
+
+Then you can create your docker image just launching: `mvn jib:dockerBuild`.
+
+TIP: you can also directly push to a remote repository without the need of having a local docker daemon, see Jib documentation for more details.
+
+IMPORTANT: in current jib version, the dependencies are build artifacts are still added to the image so you can have some "dead" code with that option.
+To avoid it you can switch to `arthur:docker` or `arthur:image`  goals.
+
+=== Arthur Docker
+
+`arthur-maven-plugin` enables to build a docker image with a plain binary built with `native-image`.
+Its usage is close to jib - with a simpler configuration - but it also can be combine with `native-image` goal:
+
+[source,bash]
+----
+mvn arthur:native-image arthur:docker
+----
+
+Output looks like:
+
+[source]
+----
+$ mvn arthur:native-image arthur:docker
+[INFO] Scanning for projects...
+[INFO]
+[INFO] ---------------------< org.apache.geronimo.Arthur:sample >---------------------
+[INFO] Building Arthur :: Sample 1.0.0-SNAPSHOT
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO]
+[INFO] --- arthur-maven-plugin:1.0.0-SNAPSHOT:native-image (default-cli) @ sample ---
+[INFO] Using GRAAL: /home/rmannibucau/.m2/repository/org/apache/geronimo/arthur/cache/graal/19.2.1/distribution_exploded
+[INFO] Extension org.apache.geronimo.arthur.maven.extension.MavenArthurExtension updated build context
+[INFO] Creating resources model '/media/data/home/rmannibucau/1_dev/connectors-se/jdbc/target/arthur_workdir/generated_configuration/resources.arthur.json'
+[INFO] Creating dynamic proxy model '/media/data/home/rmannibucau/1_dev/connectors-se/jdbc/target/arthur_workdir/generated_configuration/dynamicproxies.arthur.json'
+...same as before
+[INFO]
+[INFO] --- arthur-maven-plugin:1.0.0-SNAPSHOT:docker (default-cli) @ sample ---
+[INFO] Containerizing application with the following files:
+[INFO] 	Binary:
+[INFO] 		/opt/geronimo/arthur/sample/target/sample.graal.bin
+[INFO] Getting scratch base image...
+[INFO] Building Binary layer...
+[INFO]
+[INFO] Container entrypoint set to [/sample]
+[INFO] Container program arguments set to []
+[INFO] Loading to Docker daemon...
+[INFO] Built 'sample:1.0.0-SNAPSHOT'
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time:  01:29 min
+[INFO] Finished at: 2019-10-30T10:51:43+01:00
+[INFO] ------------------------------------------------------------------------
+----
+
+==== Configuration
+
+include::{generated_dir}/generated_docker_mojo.adoc[]
+
+=== Arthur Image
+
+Similarly to docker goal, the plugin can generate an image.
+To do it just replace the `arthur:docker` by `arthur:image`.
+
+TIP: this goal does *not* need a docker daemon on the machine, it just uses HTTP(S) communication with a registry.
+
+==== Configuration
+
+include::{generated_dir}/generated_image_mojo.adoc[]
+
+== Advanced example
+
+Just to give a real world configuration, here is how a simple JDBC application can be natified.
+It configure some resource bundle, setup h2 driver and exclude derby from the build classpath:
+
+[source,xml]
+----
+<plugin>
+    <groupId>org.apache.geronimo.arthur</groupId>
+    <artifactId>arthur-maven-plugin</artifactId>
+    <version>${arthur.version}</version>
+    <configuration>
+        <main>org.talend.components.jdbc.graalvm.MainTableNameInputEmitter</main>
+        <supportTestArtifacts>true</supportTestArtifacts> <!-- for demo only -->
+        <initializeAtBuildTime>
+            <initializeAtBuildTime>org.h2.Driver</initializeAtBuildTime>
+            <initializeAtBuildTime>org.company.MyProxyInterface1</initializeAtBuildTime>
+            <initializeAtBuildTime>org.company.MyProxyInterface2</initializeAtBuildTime>
+        </initializeAtBuildTime>
+        <bundles>
+            <bundle>
+                <name>org.company.MyProxyInterface1Messages</name>
+            </bundle>
+        </bundles>
+        <dynamicProxies>
+          <dynamicProxy> <!-- short notation -->
+              <classes>org.company.MyProxyInterface1</classes>
+          </dynamicProxy>
+          <dynamicProxy>
+              <classes> <!-- long notation -->
+                  <class>org.company.MyProxyInterface2</class>
+              </classes>
+          </dynamicProxy>
+        </dynamicProxies>
+        <excludedArtifacts> <!-- this setup uses supportTestArtifacts but we assume we don't want derby which is used in test dependencies -->
+            <excludedArtifact>org.apache.derby:derby</excludedArtifact>
+            <excludedArtifact>org.apache.derby:derbyclient</excludedArtifact>
+            <excludedArtifact>org.apache.derby:derbynet</excludedArtifact>
+        </excludedArtifacts>
+    </configuration>
+</plugin>
+----
+
+Then you can just run `mvn arthur:native-image arthur:docker` to get a ready to deploy image.
+
+---
+
+Previous: link:implementation.html[Arthur Implementation]
diff --git a/documentation/src/content/spi.adoc b/documentation/src/content/spi.adoc
new file mode 100644
index 0000000..c36393e
--- /dev/null
+++ b/documentation/src/content/spi.adoc
@@ -0,0 +1,58 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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.
+////
+= Arthur SPI
+
+Arthur SPI enables you to add custom logic to generate `native-image` configuration.
+
+== Dependencies
+
+[source,xml]
+----
+<dependency>
+  <groupId>${project.groupId}</groupId>
+  <artifactId>arthur-spi</artifactId>
+  <version>${project.version}</version>
+</dependency>
+----
+
+== Usage
+
+You must implement a `org.apache.geronimo.arthur.spi.ArthurExtension` and register it as a SPI (fully qualified name in `META-INF/services/org.apache.geronimo.arthur.spi.ArthurExtension`).
+This extension will have to implement `execute(Context)` method to register reflection, resource, ... metadata needed by `native-image` build.
+
+Here is an example:
+
+[source,java]
+----
+public class MyJaxbExtension implements ArthurExtension {
+
+    @Override
+    public void execute(final Context context) {
+        context.findAnnotatedClasses(XmlRootElement.class).stream()
+                .flatMap(clazz -> createReflectionModel(clazz))
+                .forEach(context::register);
+    }
+
+    // createReflectionModel() just instantiate a ClassReflectionModel instance
+}
+----
+
+TIP: you can use `context.finder()` to find classes based on some annotation.
+
+---
+
+Previous: link:api.html[Arthur API] Next: link:implementation.html[Arthur Implementation]
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/DocumentationGeneratorLauncher.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/DocumentationGeneratorLauncher.java
new file mode 100644
index 0000000..45d0832
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/DocumentationGeneratorLauncher.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static lombok.AccessLevel.PRIVATE;
+
+import java.beans.PropertyEditorManager;
+import java.io.PrintStream;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.apache.geronimo.arthur.documentation.editor.PathEditor;
+import org.apache.geronimo.arthur.documentation.io.ConsumedPrintStream;
+import org.apache.geronimo.arthur.documentation.io.FolderVisitor;
+import org.apache.geronimo.arthur.documentation.lang.PathPredicates;
+import org.apache.geronimo.arthur.documentation.mojo.MojoParser;
+import org.apache.geronimo.arthur.documentation.renderer.AsciidocRenderer;
+import org.tomitribe.crest.Main;
+import org.tomitribe.crest.environments.Environment;
+import org.tomitribe.crest.environments.SystemEnvironment;
+
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@NoArgsConstructor(access = PRIVATE)
+public final class DocumentationGeneratorLauncher {
+    public static void main(final String[] args) throws Exception {
+        final Map<Class<?>, Object> services = createServices().collect(toMap(Object::getClass, identity()));
+        final SystemEnvironment env = new SystemEnvironment(services) {
+            private final ConsumedPrintStream err = new ConsumedPrintStream(log::error);
+            private final ConsumedPrintStream out = new ConsumedPrintStream(log::info);
+
+            @Override
+            public PrintStream getOutput() {
+                return out;
+            }
+
+            @Override
+            public PrintStream getError() {
+                return err;
+            }
+        };
+        PropertyEditorManager.registerEditor(Path.class, PathEditor.class);
+        Environment.ENVIRONMENT_THREAD_LOCAL.set(env);
+        try {
+            new Main().main(env, args);
+        } finally {
+            // cheap cleanup solution
+            services.values().stream()
+                    .filter(AutoCloseable.class::isInstance)
+                    .map(AutoCloseable.class::cast)
+                    .forEach(it -> {
+                        try {
+                            it.close();
+                        } catch (final Exception e) {
+                            log.error(e.getMessage(), e);
+                        }
+                    });
+            Environment.ENVIRONMENT_THREAD_LOCAL.remove();
+        }
+    }
+
+    private static Stream<Object> createServices() {
+        final PathPredicates predicates = new PathPredicates();
+        return Stream.of(predicates, new AsciidocRenderer(), new FolderVisitor(predicates), new MojoParser());
+    }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/command/Generate.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/command/Generate.java
new file mode 100644
index 0000000..8c0f1b6
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/command/Generate.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.command;
+
+import static java.util.Comparator.comparing;
+import static java.util.Locale.ROOT;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.joining;
+import static org.asciidoctor.SafeMode.UNSAFE;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.geronimo.arthur.documentation.io.FolderConfiguration;
+import org.apache.geronimo.arthur.documentation.io.FolderVisitor;
+import org.apache.geronimo.arthur.documentation.mojo.MojoParser;
+import org.apache.geronimo.arthur.documentation.renderer.AsciidocRenderer;
+import org.apache.geronimo.arthur.documentation.renderer.TemplateConfiguration;
+import org.asciidoctor.AttributesBuilder;
+import org.asciidoctor.Options;
+import org.asciidoctor.OptionsBuilder;
+import org.tomitribe.crest.api.Command;
+import org.tomitribe.crest.api.Default;
+import org.tomitribe.crest.api.Defaults.DefaultMapping;
+import org.tomitribe.crest.api.Err;
+import org.tomitribe.crest.api.Option;
+import org.tomitribe.crest.api.Out;
+
+public class Generate {
+    @Command
+    public void generate(
+            @DefaultMapping(name = "location", value = "src/content")
+            @DefaultMapping(name = "includes", value = "[.+/]?.+\\.adoc$")
+            @Option("content-") final FolderConfiguration contentConfiguration,
+
+            @DefaultMapping(name = "location", value = "src/static")
+            @Option("static-") final FolderConfiguration staticConfiguration,
+
+            @DefaultMapping(name = "header", value = "src/template/header.html")
+            @DefaultMapping(name = "footer", value = "src/template/footer.html")
+            @DefaultMapping(name = "nav", value = "src/template/nav.adoc")
+            @Option("template-") final TemplateConfiguration templateConfiguration,
+
+            @Option("mojo") final List<Path> mojos,
+            @Option("output") final Path output,
+            @Option("work-directory") final Path workdir,
+
+            @Option("threads") @Default("${sys.processorCount}") final int threads,
+
+            @Out final PrintStream stdout,
+            @Err final PrintStream stderr,
+
+            final AsciidocRenderer renderer,
+            final FolderVisitor visitor,
+            final MojoParser mojoParser) {
+        stdout.println("Generating the website in " + output);
+
+        final Collection<Throwable> errors = new ArrayList<>();
+        final Executor executorImpl = threads > 1 ? newThreadPool(output, threads) : Runnable::run;
+        final Executor executor = task -> {
+            try {
+                task.run();
+            } catch (final Throwable err) {
+                err.printStackTrace(stderr);
+                synchronized (errors) {
+                    errors.add(err);
+                }
+            }
+        };
+
+        final CountDownLatch templateLatch = new CountDownLatch(1);
+        final AtomicReference<BiFunction<String, String, String>> computedTemplatization = new AtomicReference<>();
+        final Supplier<BiFunction<String, String, String>> templatize = () -> {
+            try {
+                templateLatch.await();
+            } catch (final InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            return computedTemplatization.get();
+        };
+
+        final Options adocOptions = OptionsBuilder.options()
+                .safe(UNSAFE) // we generated_dir is not safe but locally it is ok
+                .attributes(AttributesBuilder.attributes()
+                    .attribute("icons", "font")
+                    .attribute("generated_dir", workdir.toAbsolutePath().toString()))
+                .get();
+
+        executor.execute(() -> {
+            try {
+                computedTemplatization.set(compileTemplate(templateConfiguration));
+            } finally {
+                templateLatch.countDown();
+            }
+        });
+        executor.execute(() -> mojos.forEach(mojo -> {
+            try {
+                generateMojoDoc(
+                        mojo.getFileName().toString().toLowerCase(ROOT).replace("mojo.java", ""),
+                        mojoParser, mojo, workdir, stdout);
+            } catch (final IOException e) {
+                throw new IllegalStateException(e);
+            }
+        }));
+
+        final CountDownLatch visitorsFinished = new CountDownLatch(2);
+        try {
+            executor.execute(() -> {
+                try {
+                    visitor.visit(contentConfiguration, file -> executor.execute(() -> {
+                        final String name = file.getFileName().toString();
+                        final int dot = name.lastIndexOf('.');
+                        final String targetFilename = dot > 0 ? name.substring(0, dot) + ".html" : name;
+                        final Path targetFolder = contentConfiguration.getLocation()
+                                .relativize(file)
+                                .getParent();
+                        final Path target = output.resolve(targetFolder == null ?
+                                Paths.get(targetFilename) :
+                                targetFolder.resolve(targetFilename));
+                        ensureExists(target.getParent());
+                        try {
+                            final String read = read(file);
+                            final Map<String, String> metadata = renderer.extractMetadata(read);
+                            Files.write(
+                                    target,
+                                    templatize.get().apply(
+                                            metadata.getOrDefault("title", "Arthur"),
+                                            renderer.render(read, adocOptions))
+                                            .getBytes(StandardCharsets.UTF_8),
+                                    StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
+                        } catch (final IOException e) {
+                            throw new IllegalStateException(e);
+                        }
+                        stdout.println("Created '" + target + "'");
+                    }));
+                } finally {
+                    visitorsFinished.countDown();
+                }
+            });
+            executor.execute(() -> {
+                try {
+                    visitor.visit(staticConfiguration, file -> executor.execute(() -> {
+                        final Path target = output.resolve(staticConfiguration.getLocation().relativize(file));
+                        ensureExists(target.getParent());
+                        try {
+                            Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING);
+                        } catch (final IOException e) {
+                            throw new IllegalStateException(e);
+                        }
+                        stdout.println("Copied '" + target + "'");
+                    }));
+                } finally {
+                    visitorsFinished.countDown();
+                }
+            });
+        } finally {
+            try {
+                visitorsFinished.await();
+            } catch (final InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            if (ExecutorService.class.isInstance(executorImpl)) {
+                final ExecutorService service = ExecutorService.class.cast(executorImpl);
+                service.shutdown();
+                try {
+                    if (!service.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS)) {
+                        stderr.println("Exiting without the executor being properly shutdown");
+                    }
+                } catch (final InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }
+        if (!errors.isEmpty()) {
+            final IllegalStateException failed = new IllegalStateException("Execution failed");
+            errors.forEach(failed::addSuppressed);
+            throw failed;
+        }
+
+        stdout.println("Website generation done");
+    }
+
+    private void generateMojoDoc(final String marker, final MojoParser mojoParser, final Path mojo, final Path workdir,
+                                 final PrintStream stdout) throws IOException {
+        ensureExists(workdir);
+        final Collection<MojoParser.Parameter> parameters = mojoParser.extractParameters(mojo);
+        try (final Writer writer = Files.newBufferedWriter(workdir.resolve("generated_" + marker + "_mojo.adoc"))) {
+            writer.write("[opts=\"header\",role=\"table table-bordered\"]\n" +
+                    "|===\n" +
+                    "|Name|Type|Description\n\n" +
+                    parameters.stream()
+                        .sorted(comparing(MojoParser.Parameter::getName))
+                        .map(this::toLine)
+                        .collect(joining("\n\n")) +
+                    "\n|===\n");
+        }
+        stdout.println("Generated documentation for " + mojo);
+    }
+
+    private String toLine(final MojoParser.Parameter parameter) {
+        return "|" + parameter.getName() + (parameter.isRequired() ? "*" : "") +
+                "\n|" + parameter.getType() +
+                "\na|\n" + parameter.getDescription() +
+                "\n\n*Default value*: " + parameter.getDefaultValue() +
+                "\n\n*User property*: " + parameter.getProperty();
+    }
+
+    private String read(final Path file) {
+        try {
+            return Files.lines(file).collect(joining("\n"));
+        } catch (final IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private BiFunction<String, String, String> compileTemplate(final TemplateConfiguration templateConfiguration) {
+        final Collection<Function<String, String>> parts = new ArrayList<>();
+
+        final Path header = templateConfiguration.getHeader();
+        if (header != null && Files.exists(header)) {
+            final String content = read(header);
+            parts.add(s -> content + s);
+        }
+
+        final Path footer = templateConfiguration.getFooter();
+        if (footer != null && Files.exists(footer)) {
+            final String content = read(footer);
+            parts.add(s -> s + content);
+        }
+        final Function<String, String> fn = parts.stream().reduce(identity(), Function::andThen);
+        return (title, html) -> fn.apply(html).replace("${arthurTemplateTitle}", title);
+    }
+
+    private ExecutorService newThreadPool(@Option("output") Path output, @Default("${sys.processorCount}") @Option("threads") int threads) {
+        return new ThreadPoolExecutor(threads, threads, 1, MINUTES, new LinkedBlockingQueue<>(), new ThreadFactory() {
+            private final AtomicInteger counter = new AtomicInteger();
+
+            @Override
+            public Thread newThread(final Runnable worker) {
+                return new Thread(worker, "arthur-generator-" + counter.incrementAndGet() + "-[" + output + "]");
+            }
+        });
+    }
+
+    private void ensureExists(final Path dir) {
+        if (!Files.exists(dir)) {
+            try {
+                Files.createDirectories(dir);
+            } catch (final IOException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/editor/PathEditor.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/editor/PathEditor.java
new file mode 100644
index 0000000..956e58a
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/editor/PathEditor.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.editor;
+
+import java.nio.file.Paths;
+
+import org.tomitribe.util.editor.AbstractConverter;
+
+public class PathEditor extends AbstractConverter {
+    @Override
+    protected Object toObjectImpl(final String text) {
+        return Paths.get(text);
+    }
+
+    protected String toStringImpl(final Object value) {
+        return String.valueOf(value);
+    }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/interpolation/Sys.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/interpolation/Sys.java
new file mode 100644
index 0000000..667444e
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/interpolation/Sys.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.interpolation;
+
+import java.lang.reflect.Method;
+
+import org.tomitribe.crest.cmds.targets.Target;
+import org.tomitribe.crest.contexts.DefaultsContext;
+
+public class Sys implements DefaultsContext {
+    @Override
+    public String find(final Target target, final Method method, final String key) {
+        switch (key) {
+            case ".processorCount":
+                return Integer.toString(Math.max(1, Runtime.getRuntime().availableProcessors()));
+            default:
+                return null;
+        }
+    }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/ConsumedPrintStream.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/ConsumedPrintStream.java
new file mode 100644
index 0000000..d39ec31
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/ConsumedPrintStream.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.io;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Consumer;
+
+public class ConsumedPrintStream extends PrintStream {
+    private final Consumer<String> consumer;
+
+    private final ByteArrayOutputStream buffer;
+
+    public ConsumedPrintStream(final Consumer<String> consumer) {
+        super(new ByteArrayOutputStream());
+        this.consumer = consumer;
+        this.buffer = ByteArrayOutputStream.class.cast(out);
+    }
+
+    private void onEnd() {
+        final byte[] bytes = buffer.toByteArray();
+        if (bytes.length > 0) {
+            consumer.accept(new String(bytes, 0, bytes[bytes.length - 1] == '\n' ? bytes.length - 1 : bytes.length, StandardCharsets.UTF_8));
+            buffer.reset();
+        }
+    }
+
+    @Override
+    public void println(final String content) {
+        super.println(content);
+        onEnd();
+    }
+
+    @Override
+    public void println() {
+        super.println();
+        onEnd();
+    }
+
+    @Override
+    public void println(final boolean x) {
+        super.println(x);
+        onEnd();
+    }
+
+    @Override
+    public void println(final char x) {
+        super.println(x);
+        onEnd();
+    }
+
+    @Override
+    public void println(final int x) {
+        super.println(x);
+        onEnd();
+    }
+
+    @Override
+    public void println(final long x) {
+        super.println(x);
+        onEnd();
+    }
+
+    @Override
+    public void println(final float x) {
+        super.println(x);
+        onEnd();
+    }
+
+    @Override
+    public void println(final double x) {
+        super.println(x);
+        onEnd();
+    }
+
+    @Override
+    public void println(final char[] x) {
+        super.println(x);
+        onEnd();
+    }
+
+    @Override
+    public void println(final Object x) {
+        super.println(x);
+        onEnd();
+    }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderConfiguration.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderConfiguration.java
new file mode 100644
index 0000000..281f680
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.io;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import org.tomitribe.crest.api.Default;
+import org.tomitribe.crest.api.Option;
+import org.tomitribe.crest.api.Options;
+
+import lombok.Data;
+
+@Data
+@Options
+public class FolderConfiguration {
+    private final Path location;
+    private final List<String> includes;
+    private final List<String> excludes;
+
+    public FolderConfiguration(@Option("location") final Path location,
+                               @Option("includes") final List<String> includes,
+                               @Option("excludes") @Default("^\\..+") final List<String> excludes) {
+        this.location = location;
+        this.includes = includes;
+        this.excludes = excludes;
+    }
+}
\ No newline at end of file
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderVisitor.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderVisitor.java
new file mode 100644
index 0000000..fdbfc00
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/io/FolderVisitor.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.io;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import org.apache.geronimo.arthur.documentation.lang.PathPredicates;
+
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class FolderVisitor {
+    private final PathPredicates predicates;
+
+    public void visit(final FolderConfiguration configuration,
+                      final Consumer<Path> consumer) {
+        final Path root = requireNonNull(configuration.getLocation(), "Missing location");
+        if (Files.exists(root)) {
+            final Predicate<Path> filter = predicates.createFilter(configuration.getIncludes(), configuration.getExcludes());
+            try {
+                Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
+                    @Override
+                    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+                        if (filter.test(root.relativize(file))) {
+                            consumer.accept(file);
+                        }
+                        return super.visitFile(file, attrs);
+                    }
+                });
+            } catch (final IOException e) {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/lang/PathPredicates.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/lang/PathPredicates.java
new file mode 100644
index 0000000..0309c0c
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/lang/PathPredicates.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.lang;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+public class PathPredicates {
+    public Predicate<Path> createFilter(final List<String> includes, final List<String> excludes) {
+        return createFilter(includes).and(createFilter(excludes).negate());
+    }
+
+    public Predicate<Path> createFilter(final List<String> patterns) {
+        return patterns == null || patterns.isEmpty() ?
+                p -> true :
+                patterns.stream()
+                        .map(Pattern::compile)
+                        .map(Pattern::asPredicate)
+                        .map(pattern -> (Predicate<Path>) path -> pattern.test(path.toString()))
+                        .reduce(p -> false, Predicate::or);
+    }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/mojo/MojoParser.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/mojo/MojoParser.java
new file mode 100644
index 0000000..2017fd2
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/mojo/MojoParser.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.mojo;
+
+import static java.util.stream.Collectors.toList;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import lombok.Data;
+
+public class MojoParser {
+    // simplified java parser for *our* code
+    public Collection<Parameter> extractParameters(final Path file) throws IOException {
+        final Collection<Parameter> parameters = new ArrayList<>();
+        String parent = null;
+        try (final BufferedReader reader = Files.newBufferedReader(file)) {
+            String line;
+            boolean jdoc = false;
+            final StringBuilder javadoc = new StringBuilder();
+            while ((line = reader.readLine()) != null) {
+                line = line.trim();
+                if (line.startsWith("public class ") || line.startsWith("public abstract class ")) {
+                    final int ext = line.indexOf("extends ");
+                    if (ext > 0) {
+                        parent = line.substring(ext + "extends ".length(), line.indexOf("{")).trim();
+                    }
+                    if ("AbstractMojo".equals(parent)) {
+                        parent = null;
+                    }
+                } else if (line.startsWith("/**")) {
+                    jdoc = true;
+                } else if (line.endsWith("*/")) {
+                    jdoc = false;
+                } else if (jdoc) {
+                    javadoc.append(line.startsWith("*") ? line.substring(1).trim() : line);
+                } else if (javadoc.length() > 0 && line.startsWith("@Parameter")) {
+                    final StringBuilder annotation = new StringBuilder(line);
+                    while ((line = reader.readLine()) != null) {
+                        line = line.trim();
+                        if (line.startsWith("private ") || line.startsWith("protected ")) {
+                            final int nameStart = line.lastIndexOf(' ') + 1;
+                            final String config = annotation.toString();
+                            final int prefixLen = line.indexOf(' ') + 1;
+                            String type = line.substring(prefixLen, line.indexOf(" ", prefixLen)).trim();
+                            final int generics = type.indexOf('<');
+                            if (generics > 0) {
+                                type = type.substring(0, generics);
+                            }
+                            parameters.add(new Parameter(
+                                    line.substring(nameStart, line.length() - ";".length()),
+                                    type,
+                                    config.contains("required = true"),
+                                    find("property =", config),
+                                    javadoc.toString(),
+                                    find("defaultValue =", config)));
+                            javadoc.setLength(0);
+                            break;
+                        } else {
+                            annotation.append(line);
+                        }
+                    }
+                } else if (line.startsWith("package")) { // we have the header before
+                    javadoc.setLength(0);
+                }
+            }
+        }
+        if (parent != null) {
+            return Stream.concat(
+                    parameters.stream(),
+                    extractParameters(file.getParent().resolve(parent + ".java")).stream())
+                    .collect(toList());
+        }
+        return parameters;
+    }
+
+    private String find(final String prefix, final String value) {
+        int start = value.indexOf(prefix);
+        if (start < 0) {
+            return "-";
+        }
+        start += prefix.length();
+        while (Character.isWhitespace(value.charAt(start)) || value.charAt(start) == '"') {
+            start++;
+        }
+        final int end = value.indexOf('"', start);
+        if (end > 0) {
+            return value.substring(start, end).trim();
+        }
+        return "-";
+    }
+
+    @Data
+    public static class Parameter {
+        private final String name;
+        private final String type;
+        private final boolean required;
+        private final String property;
+        private final String description;
+        private final String defaultValue;
+    }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/AsciidocRenderer.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/AsciidocRenderer.java
new file mode 100644
index 0000000..b5a3593
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/AsciidocRenderer.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.renderer;
+
+import static java.util.Collections.singletonMap;
+
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import org.asciidoctor.Asciidoctor;
+import org.asciidoctor.Options;
+import org.asciidoctor.ast.DocumentHeader;
+import org.asciidoctor.jruby.internal.JRubyAsciidoctor;
+
+public class AsciidocRenderer implements AutoCloseable {
+    private Asciidoctor asciidoctor;
+
+    private final CountDownLatch latch = new CountDownLatch(1);
+
+    public AsciidocRenderer() {
+        new Thread(() -> { // this is insanely slow so let's do it in background
+            asciidoctor = JRubyAsciidoctor.create();
+            latch.countDown();
+        }, getClass().getName() + '-' + hashCode()).start();
+    }
+
+    public String render(final String input, final Options options) {
+        await();
+        return asciidoctor.convert(input, options);
+    }
+
+    @Override
+    public void close() {
+        await();
+        asciidoctor.shutdown();
+    }
+
+    private void await() {
+        try {
+            latch.await();
+        } catch (final InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    public Map<String, String> extractMetadata(final String content) {
+        await();
+        final DocumentHeader header = asciidoctor.readDocumentHeader(content);
+        return singletonMap("title", header.getPageTitle());
+    }
+}
diff --git a/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/TemplateConfiguration.java b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/TemplateConfiguration.java
new file mode 100644
index 0000000..d878483
--- /dev/null
+++ b/documentation/src/main/java/org/apache/geronimo/arthur/documentation/renderer/TemplateConfiguration.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.renderer;
+
+import java.nio.file.Path;
+
+import org.tomitribe.crest.api.Option;
+import org.tomitribe.crest.api.Options;
+
+import lombok.Data;
+
+@Data
+@Options
+public class TemplateConfiguration {
+    private final Path header;
+    private final Path footer;
+
+    public TemplateConfiguration(@Option("header") final Path header,
+                                 @Option("footer") final Path footer) {
+        this.header = header;
+        this.footer = footer;
+    }
+}
diff --git a/documentation/src/main/resources/META-INF/services/org.tomitribe.crest.contexts.DefaultsContext b/documentation/src/main/resources/META-INF/services/org.tomitribe.crest.contexts.DefaultsContext
new file mode 100644
index 0000000..c88588c
--- /dev/null
+++ b/documentation/src/main/resources/META-INF/services/org.tomitribe.crest.contexts.DefaultsContext
@@ -0,0 +1 @@
+org.apache.geronimo.arthur.documentation.interpolation.Sys
diff --git a/documentation/src/main/resources/crest-commands.txt b/documentation/src/main/resources/crest-commands.txt
new file mode 100644
index 0000000..7972f67
--- /dev/null
+++ b/documentation/src/main/resources/crest-commands.txt
@@ -0,0 +1 @@
+org.apache.geronimo.arthur.documentation.command.Generate
diff --git a/documentation/src/static/css/arthur.css b/documentation/src/static/css/arthur.css
new file mode 100644
index 0000000..372cdb0
--- /dev/null
+++ b/documentation/src/static/css/arthur.css
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+* {
+  text-align: justify;
+}
+.page-header {
+  padding-top: 100px;
+  min-height: calc(100vh - 50px);
+}
+#footer {
+  font-style: italic;
+  font-size: smaller;
+}
+table > thead > tr > th {
+  background-color: lightslategray;
+}
\ No newline at end of file
diff --git a/documentation/src/static/js/arthur.js b/documentation/src/static/js/arthur.js
new file mode 100644
index 0000000..d87d6b5
--- /dev/null
+++ b/documentation/src/static/js/arthur.js
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+(function() {
+  $('code[class^="language-"]').addClass('prettyprint');
+  $('thead').addClass('thead-light');
+  prettyPrint();
+})();
diff --git a/documentation/src/template/footer.html b/documentation/src/template/footer.html
new file mode 100644
index 0000000..95d52eb
--- /dev/null
+++ b/documentation/src/template/footer.html
@@ -0,0 +1,22 @@
+</div>
+
+<div id="footer">
+    <div class="container">
+        Copyright &copy; 1999-2019 The Apache Software Foundation, Licensed under the Apache License, Version 2.0.
+        Apache Geronimo, Geronimo, Apache, the Apache feather logo, and the Apache Geronimo project logo are trademarks
+        of
+        The Apache Software Foundation. All other marks mentioned may be trademarks or registered trademarks of their
+        respective owners.
+    </div>
+</div>
+
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
+        integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
+<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js"
+        integrity="sha384-o+RDsa0aLu++PJvFqy8fFScvbHFLtbvScb8AjopnFD+iEQ7wo/CG0xlczd+2O/em"
+        crossorigin="anonymous"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js"
+        integrity="sha256-SHXnnZAbgSEf+OBhDLR7I2mx9vNZAIzPeCGhxRV+VQw=" crossorigin="anonymous"></script>
+<script src="js/arthur.js"></script>
+</body>
+</html>
diff --git a/documentation/src/template/header.html b/documentation/src/template/header.html
new file mode 100644
index 0000000..69c7b2d
--- /dev/null
+++ b/documentation/src/template/header.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8"/>
+    <title>${arthurTemplateTitle}</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="Apache Geronimo Arthur Site">
+    <meta name="author" content="Apache">
+    <meta name="keywords" content="Apache Geronimo Arthur">
+    <meta name="generator" content="Arthur Site Generator">
+
+    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css" rel="stylesheet"
+          integrity="sha384-Smlep5jCw/wG7hdkwQ/Z5nLIefveQRIY9nfy6xoR1uRYBtpZgI6339F5dgvm/e9B" crossorigin="anonymous">
+    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"
+          integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css"
+          integrity="sha256-IPtNBA1od/cGBfXTxYDxuT5+Y2BKy14o6j0FaIXDmYk=" crossorigin="anonymous"/>
+    <link href="https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/flatly/bootstrap.min.css" rel="stylesheet"
+          integrity="sha384-T5jhQKMh96HMkXwqVMSjF3CmLcL1nT9//tCqu9By5XSdj7CwR0r+F3LTzUdfkkQf" crossorigin="anonymous">
+    <link rel="stylesheet" href="css/arthur.css"/>
+
+    <!--[if lt IE 9]>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"
+            integrity="sha256-3Jy/GbSLrg0o9y5Z5n1uw0qxZECH7C6OQpVBgNFYa0g=" crossorigin="anonymous"></script>
+    <![endif]-->
+</head>
+<body cz-shortcut-listen="true">
+
+<div class="navbar navbar-expand-lg fixed-top navbar-dark bg-primary">
+    <div class="container">
+        <a href="index.html" class="navbar-brand">Apache Geronimo Arthur</a>
+        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
+                aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+            <span class="navbar-toggler-icon"></span>
+        </button>
+        <div class="collapse navbar-collapse justify-content-end" id="navbarResponsive">
+            <ul class="navbar-nav">
+                <li class="nav-item">
+                    <a class="nav-link" href="https://www.apache.org/">Apache</a>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link" href="documentation.html">Documentation</a>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link" href="community.html">Community</a>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link" href="downloads.html">Downloads</a>
+                </li>
+                <li class="nav-item dropdown">
+                    <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" id="asf-links">ASF <span
+                            class="caret"></span></a>
+                    <div class="dropdown-menu" aria-labelledby="asf-links">
+                        <a class="dropdown-item" href="https://www.apache.org/licenses/">License</a>
+                        <a class="dropdown-item"
+                           href="https://www.apache.org/foundation/sponsorship.html">Sponsorship</a>
+                        <a class="dropdown-item" href="https://www.apache.org/foundation/thanks.html">Thanks</a>
+                        <div class="dropdown-divider"></div>
+                        <a class="dropdown-item" href="https://www.apache.org/security/">Security</a>
+                    </div>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+
+<div class="container page-header">
\ No newline at end of file
diff --git a/documentation/src/test/java/org/apache/geronimo/arthur/documentation/lang/PathPredicatesTest.java b/documentation/src/test/java/org/apache/geronimo/arthur/documentation/lang/PathPredicatesTest.java
new file mode 100644
index 0000000..3782857
--- /dev/null
+++ b/documentation/src/test/java/org/apache/geronimo/arthur/documentation/lang/PathPredicatesTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.arthur.documentation.lang;
+
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+
+class PathPredicatesTest {
+    @Test
+    void nullValue() {
+        final Predicate<Path> adoc = new PathPredicates().createFilter(null);
+        assertTrue(adoc.test(Paths.get("foo.adoc")));
+        assertTrue(adoc.test(Paths.get(".foo.adoc")));
+        assertTrue(adoc.test(Paths.get(".git")));
+        assertTrue(adoc.test(Paths.get("whatever")));
+    }
+
+    @Test
+    void simple() {
+        final Predicate<Path> adoc = new PathPredicates().createFilter(singletonList(".+\\.adoc"));
+        assertTrue(adoc.test(Paths.get("foo.adoc")));
+        assertTrue(adoc.test(Paths.get(".foo.adoc")));
+        assertFalse(adoc.test(Paths.get(".git")));
+        assertFalse(adoc.test(Paths.get("whatever")));
+
+        final Predicate<Path> dotted = new PathPredicates().createFilter(singletonList("^\\..+"));
+        assertFalse(dotted.test(Paths.get("foo.adoc")));
+        assertTrue(dotted.test(Paths.get(".foo.adoc")));
+        assertTrue(dotted.test(Paths.get(".git")));
+        assertFalse(dotted.test(Paths.get("whatever")));
+    }
+
+    @Test
+    void includeExclude() {
+        final Predicate<Path> filter = new PathPredicates().createFilter(singletonList(".+\\.adoc"), singletonList("^\\..+"));
+        assertTrue(filter.test(Paths.get("foo.adoc")));
+        assertFalse(filter.test(Paths.get(".foo.adoc")));
+        assertFalse(filter.test(Paths.get(".git")));
+        assertFalse(filter.test(Paths.get("whatever")));
+    }
+
+    @Test
+    void includeExcludeNullInclude() {
+        final Predicate<Path> filter = new PathPredicates().createFilter(null, singletonList("^\\..+"));
+        assertTrue(filter.test(Paths.get("foo.adoc")));
+        assertFalse(filter.test(Paths.get(".foo.adoc")));
+        assertFalse(filter.test(Paths.get(".git")));
+        assertTrue(filter.test(Paths.get("whatever")));
+    }
+}
diff --git a/pom.xml b/pom.xml
index 041fe2e..d510eca 100644
--- a/pom.xml
+++ b/pom.xml
@@ -50,6 +50,7 @@
     <module>arthur-impl</module>
     <module>arthur-maven-plugin</module>
     <module>arthur-spi</module>
+    <module>documentation</module>
   </modules>
 
   <dependencies>
@@ -121,6 +122,8 @@
           <excludes>
             <exclude>**/*/MANIFEST.MF</exclude>
             <exclude>**/META-INF/services/*</exclude>
+            <exclude>**/crest-commands.txt</exclude>
+            <exclude>**/*.html</exclude>
           </excludes>
         </configuration>
         <executions>