diff --git a/_data/guides.yaml b/_data/guides.yaml
index 865481e8..6578d844 100644
--- a/_data/guides.yaml
+++ b/_data/guides.yaml
@@ -55,4 +55,10 @@ categories:
- title: Deploying WildFly using Ansible
url: /guides/automate-with-ansible
description: Learn how to automate WildFly deployments with Ansible.
+ - category: Datasources
+ cat-id: datasources
+ guides:
+ - title: Integrating with a PostgreSQL database
+ url: /guides/database-integrating-with-postgresql
+ description: Learn how to configure a datasource to connect a PostgreSQL database.
diff --git a/guides/database-integrating-with-postgresql.adoc b/guides/database-integrating-with-postgresql.adoc
new file mode 100644
index 00000000..723105e5
--- /dev/null
+++ b/guides/database-integrating-with-postgresql.adoc
@@ -0,0 +1,739 @@
+= Integrating with a PostgreSQL database
+:summary: Learn how to configure a datasource to connect a PostgreSQL database
+:includedir: _includes
+include::{includedir}/_attributes.adoc[]
+:prerequisites-time: 20
+:postgre-sql-user: postgres
+:postgre-sql-password: admin
+:postgre-sql-port: 5432
+:postgre-sql-host: localhost
+:postgre-sql-database: bookstore_db
+:postgre-docker-image: docker.io/library/postgres
+:postgre-sql-kubernetes-service-name: postgres-service
+
+In this guide, you will learn how to configure WildFly to connect to a PostgreSQL database. You will create a simple Book Store API application to manage books stored in the database using a https://jakarta.ee/specifications/restful-ws/[Jakarta RESTful Web Services (JAX-RS), window=_blank].
+
+include::{includedir}/_prerequisites.adoc[]
+
+* Docker or any Open Container Initiative engine installed. This guide uses https://podman.io/[Podman, window=_blank].
+
+== Database
+
+=== PostgreSQL
+
+We will use PostgreSQL as database server in its containerized version: see https://hub.docker.com/_/postgres[PostgreSQL Official Image, window=_blank].
+
+Start PostgreSQL database in a container with:
+
+[source,bash,subs="normal"]
+----
+podman run --rm --name bookstore \
+ -p {postgre-sql-port}:{postgre-sql-port} \
+ -e POSTGRES_PASSWORD={postgre-sql-password} \
+ -e POSTGRES_USER={postgre-sql-user} \
+ -e POSTGRES_DB={postgre-sql-database} \
+ {postgre-docker-image}
+----
+
+NOTE: we started the container with the `--rm` so it can be disposed automatically when we stop it.
+
+
+== Application
+
+=== Create a new Maven project
+
+We are going to use the *WildFly Getting Started Archetype* to create the base structure of our Book Store API.
+
+Open a new terminal window and create a new project using the WildFly Getting Started Archetype:
+
+[source,bash,subs="normal"]
+----
+mvn archetype:generate \
+ -DarchetypeGroupId=org.wildfly.archetype \
+ -DarchetypeArtifactId=wildfly-getting-started-archetype \
+ -DdefaultClassPrefix=BookStore \
+ -DartifactId=bookstore \
+ -Dversion=1.0.0 \
+ -DinteractiveMode=false
+----
+Remove the following files from the base project since we are not going to use them:
+
+[source,bash]
+----
+cd bookstore
+rm src/main/java/org/wildfly/examples/BookStoreService.java
+rm src/main/java/org/wildfly/examples/BookStoreEndpoint.java
+rm src/test/java/org/wildfly/examples/BookStoreApplicationIT.java
+rm src/test/java/org/wildfly/examples/BookStoreServiceIT.java
+----
+
+=== pom.xml
+
+==== Jakarta EE dependencies:
+
+Add the following dependencies to the `pom.xml` file:
+
+[source,xml]
+----
+
+ jakarta.persistence
+ jakarta.persistence-api
+ provided
+
+
+ jakarta.transaction
+ jakarta.transaction-api
+ provided
+
+
+ jakarta.validation
+ jakarta.validation-api
+ provided
+
+----
+
+==== Configure WildFly Datasource and trimming server capabilities:
+To connect to the database we need to configure the https://docs.wildfly.org/32/Admin_Guide.html#DataSource[WildFly Datasource Subsystem,window=_blank] and install the PostgreSQL driver into the WildFly server. The https://github.com/wildfly-extras/wildfly-datasources-galleon-pack[WildFly Datasource Galleon Pack, window=_blank] contains a set of https://github.com/wildfly-extras/wildfly-datasources-galleon-pack/blob/main/doc/postgresql/README.md[Galleon Layers, window=_blank] that provide *JDBC drivers* and *WildFly Datasource Subsystem* configurations for various databases. For this guide, we will use the `postgresql-default-datasource` Galleon layer that will configure a PostgreSQL datasource as the default datasource for the server.
+
+In addition to the Galleon Layers to configure the datasource and installing the drivers, we also want to trim the WildFly server to remove any unnecessary subsystems and features we don't need. That will reduce the server footprint and make it more secured. This task can be done by selecting the appropriated https://docs.wildfly.org/32/Galleon_Guide.html#wildfly_galleon_layers[Galleon Layers, window=_blank] shipped with any WildFly distribution. However, instead of adding a static list of Galleon Layers, we are going to configure the `wildfly-maven-plugin` plugin to discover the required layers automatically for us.
+
+Replace the current `wildfly-maven-plugin` configuration provided by the getting starter guide with the following one in the `pom.xml` file:
+
+[source,xml]
+----
+
+ org.wildfly.plugins
+ wildfly-maven-plugin
+ ${version.wildfly.maven.plugin}
+
+
+
+ postgresql:default
+
+
+
+
+
+
+ package
+
+
+
+
+----
+In the above configuration, behind scenes the `wildfly-maven-plugin` is using https://docs.wildfly.org/wildfly-glow/[WildFly Glow, window=_blank] to discover automatically the required Galleon Layers for our application. The `discover-provisioning-info` configuration tells the plugin to discover the required layers by inspecting our application code. By using `postgresql:default` addon, we are specifying we want to use a PostgreSQL database, and we want to configure it as the default datasource for the server.
+
+=== persistence.xml
+This file is used to configure the Jakarta Persistence Api (JPA) unit and the schema generation strategy. In this guide, we are using the `drop-and-create` strategy to drop the existing schema and create a new one every time the application starts. For a production environment, you should use a more appropriate strategy to avoid data loss.
+
+Create the following `persistence.xml` file in the `src/main/resources/META-INF` directory:
+
+[source,xml]
+----
+
+
+
+
+
+
+
+
+----
+NOTE: We don't need to specify the name of the Datasource by using ``. In absence of this property, JPA will use the default datasource configured in the server.
+
+=== Configure RESTful Web Services (JAX-RS) application
+
+The `BookStoreApplication` class acts as a configuration class for the JAX-RS application. It essentially tells the JAX-RS runtime that this is a JAX-RS application and provides the base path for the application's RESTful web services.
+
+Modify it as follows to specify `/api` as the base URL for our JAX-RS Web Service:
+
+[source,java]
+----
+package org.wildfly.examples;
+
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.core.Application;
+
+@ApplicationPath("/api")
+public class BookStoreApplication extends Application {
+}
+----
+
+=== Book Entity
+The `Book` entity represents a book record in the database.
+
+Create a new class `Book` in the `src/main/java/org/wildfly/examples/books` directory with the following content:
+
+[source,java]
+----
+package org.wildfly.examples.books;
+
+import java.util.Objects;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.PositiveOrZero;
+
+@Entity
+@Table(name = "books")
+public class Book {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotBlank
+ @Column(nullable = false)
+ private String title;
+
+ @NotBlank
+ @Column(nullable = false)
+ private String author;
+
+ @NotBlank
+ @Column(nullable = false)
+ private String isbn;
+
+ @PositiveOrZero
+ @Column
+ private double price;
+
+ public Book() {
+ }
+
+ public Book(String title, String author, String isbn, double price) {
+ this.title = title;
+ this.author = author;
+ this.isbn = isbn;
+ this.price = price;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public String getIsbn() {
+ return isbn;
+ }
+
+ public void setIsbn(String isbn) {
+ this.isbn = isbn;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Book book = (Book) o;
+ return Double.compare(price, book.price) == 0 && Objects.equals(id, book.id) && Objects.equals(title, book.title) && Objects.equals(author, book.author) && Objects.equals(isbn, book.isbn);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, title, author, isbn, price);
+ }
+
+ @Override
+ public String toString() {
+ return "Book{" +
+ "id=" + id +
+ ", title='" + title + '\'' +
+ ", author='" + author + '\'' +
+ ", isbn='" + isbn + '\'' +
+ ", price=" + price +
+ '}';
+ }
+}
+----
+
+=== BookResource
+The `BookResource` is the web service that exposes the book records as JSon objects.
+
+Create a new class `BookResource` in the `src/main/java/org/wildfly/examples/books` directory with the following content:
+
+[source,java]
+----
+package org.wildfly.examples.books;
+
+import java.net.URI;
+import java.util.List;
+
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.transaction.Transactional;
+import jakarta.validation.Valid;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriInfo;
+
+@Path("/books")
+@RequestScoped
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public class BookResource {
+
+ @PersistenceContext
+ EntityManager em;
+
+ @Context
+ UriInfo uriInfo;
+
+ @GET
+ public Response getAll() {
+ List all = em.createQuery("SELECT b FROM Book b", Book.class)
+ .getResultList();
+
+ return Response.ok()
+ .entity(all)
+ .build();
+ }
+
+ @GET
+ @Path("/{id}")
+ public Response getById(@PathParam("id") Long id) {
+ Book book = em.find(Book.class, id);
+ if (book == null) {
+ throw new NotFoundException("Book with id " + id + " not found");
+ }
+
+ return Response.ok()
+ .entity(book)
+ .build();
+ }
+
+ @POST
+ @Transactional
+ public Response create(@Valid Book book) {
+ em.persist(book);
+
+ final URI location = uriInfo.getBaseUriBuilder()
+ .path(BookResource.class)
+ .path(book.getId().toString())
+ .build();
+
+ return Response.created(location)
+ .entity(book)
+ .build();
+ }
+
+ @PUT
+ @Path("/{id}")
+ @Transactional
+ public Response update(@PathParam("id") Long id, @Valid Book book) {
+ Book existing = em.find(Book.class, id);
+ if (existing == null) {
+ throw new NotFoundException("Book with id " + id + " not found");
+ }
+ existing.setAuthor(book.getAuthor());
+ existing.setTitle(book.getTitle());
+ existing.setIsbn(book.getIsbn());
+ existing.setPrice(book.getPrice());
+
+ return Response.ok()
+ .entity(existing)
+ .build();
+ }
+
+ @DELETE
+ @Path("/{id}")
+ @Transactional
+ public Response delete(@PathParam("id") Long id) {
+ Book book = em.find(Book.class, id);
+ if (book == null) {
+ throw new NotFoundException("Book with id " + id + " not found");
+ }
+ em.remove(book);
+
+ return Response.noContent()
+ .build();
+ }
+}
+----
+
+== Start the application
+
+Now we should be ready to start our application and interact with the database. First, build the application using Maven:
+
+[source,bash,subs="normal"]
+----
+mvn clean package
+----
+
+Notice how WildFly Glow gives us information about the Feature packs and Galleon layers discovered. It also provides some hints about required environment variables:
+
+[source,bash]
+----
+[INFO] --- wildfly:5.0.0.Final:package (default) @ bookstore ---
+[INFO] Glow is scanning...
+[INFO] Glow scanning DONE.
+[INFO] context: bare-metal
+[INFO] enabled profile: none
+[INFO] galleon discovery
+[INFO] - feature-packs
+ org.wildfly:wildfly-galleon-pack:32.0.1.Final
+ org.wildfly:wildfly-datasources-galleon-pack:8.0.0.Final
+- layers
+ ee-core-profile-server
+ jaxrs
+ jpa
+ postgresql-default-datasource
+
+[INFO] enabled add-ons
+[INFO] - postgresql : Documentation in https://github.com/wildfly-extras/wildfly-datasources-galleon-pack
+- postgresql:default : Documentation in https://github.com/wildfly-extras/wildfly-datasources-galleon-pack
+
+[INFO] identified fixes
+[INFO] * no default datasource found error is fixed
+ - add-on postgresql:default fixes the problem but you need to set the strongly suggested configuration.
+
+[WARNING] strongly suggested configuration at runtime
+[WARNING]
+postgresql-datasource environment variables:
+ - POSTGRESQL_DATABASE=Defines the database name to be used in the datasource’s `connection-url` property.
+ - POSTGRESQL_PASSWORD=Defines the password for the datasource.
+ - POSTGRESQL_USER=Defines the username for the datasource.
+[WARNING]
+postgresql-default-datasource environment variables:
+ - POSTGRESQL_DATABASE=Defines the database name to be used in the datasource’s `connection-url` property.
+ - POSTGRESQL_PASSWORD=Defines the password for the datasource.
+ - POSTGRESQL_USER=Defines the username for the datasource.
+----
+
+Now create the required *environment variables* used by WildFly to connect to the PostgreSQL database and start the server:
+
+[source,bash,subs="normal"]
+----
+export POSTGRESQL_USER={postgre-sql-user}
+export POSTGRESQL_PASSWORD={postgre-sql-password}
+export POSTGRESQL_DATABASE={postgre-sql-database}
+
+./target/server/bin/standalone.sh
+...
+11:34:49,242 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 32.0.1.Final (WildFly Core 24.0.1.Final) started in 2118ms - Started 295 of 366 services (139 services are lazy, passive or on-demand) - Server configuration file in use: standalone.xml
+----
+
+== Check the application
+
+We have our application running at http://localhost:8080/. Let's now interact with it using the following endpoints to Create, Read, Update and Delete books. We will use the `curl` utility to interact with the application.
+
+=== Create a book
+To create a new book, execute a POST request to the `/api/books` endpoint with the book information:
+
+[source,bash]
+----
+$ curl -v -X POST http://localhost:8080/api/books -H "Content-Type: application/json" -d '
+{
+"author": "Jules Verne",
+"isbn": "10-0760765197",
+"price": 9.99,
+"title": "From the Earth to the Moon"
+}'
+----
+
+If you inspect the response, you will see the URL of the newly created book gets returned under the location header:
+
+[source,bash]
+----
+Location: http://localhost:8080/api/books/1
+----
+
+You can use the location to check the book you have just created:
+
+[source,bash]
+----
+$ curl http://localhost:8080/api/books/1
+----
+[source,json]
+----
+{
+ "author": "Jules Verne",
+ "id": 1,
+ "isbn": "10-0760765197",
+ "price": 9.99,
+ "title": "From the Earth to the Moon"
+}
+----
+
+=== Read all the books
+To list all the books, execute a GET request to the `/api/books` endpoint:
+
+[source,bash]
+----
+$ curl http://localhost:8080/api/books
+----
+
+It will return the list of books of our database:
+
+[source,json]
+----
+[
+ {
+ "author": "Jules Verne",
+ "id": 1,
+ "isbn": "10-0760765197",
+ "price": 9.99,
+ "title": "From the Earth to the Moon"
+ }
+]
+----
+
+=== Update a book
+To update a book, execute a PUT request to the `/api/books/{id}` endpoint with the book information you want. For example to change the price of the recent book we have recently created, execute the following:
+
+[source,bash]
+----
+$ curl -X PUT http://localhost:8080/api/books/1 -H "Content-Type: application/json" -d '
+{
+"author": "Jules Verne",
+"isbn": "10-0760765197",
+"price": 10.99,
+"title": "From the Earth to the Moon"
+}'
+----
+
+=== Delete a book
+To delete a book, execute a DELETE request to the `/api/books/{id}` endpoint with the book id you want to delete. For example, to delete the book we have recently created:
+
+[source,bash]
+----
+$ curl -X DELETE http://localhost:8080/api/books/1
+----
+
+==== Stop the application
+
+To stop the application, press `Ctrl+C` in the terminal where the server is running.
+
+
+== Test Cases
+
+Until now, we have verified the application manually. The following steps will guide you with the required changes to test our application using the Arquillian framework.
+
+=== pom.xml
+We need to have a JSON provider available on the test classpath to convert Book objects to JSON and vice versa.
+
+Add the following dependency to the `pom.xml` file:
+
+[source,xml]
+----
+
+ org.jboss.resteasy
+ resteasy-jackson2-provider
+ test
+
+----
+
+=== Book Resource test case
+
+Create a new class `BookResourceIT` in the `src/test/java/org/wildfly/examples/books` directory with the following content:
+
+[source,java]
+----
+package org.wildfly.examples.books;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.net.URI;
+import java.util.List;
+
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.ClientBuilder;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@RunAsClient
+@ExtendWith(ArquillianExtension.class)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class BookResourceIT {
+
+ @Test
+ @Order(1)
+ public void testHelloEndpoint() {
+ try (Client client = ClientBuilder.newClient()) {
+ Response response = client
+ .target(URI.create("http://localhost:8080/"))
+ .path("/")
+ .request()
+ .get();
+
+ assertEquals(200, response.getStatus());
+ }
+ }
+
+ @Test
+ @Order(2)
+ public void create() {
+ try (Client client = ClientBuilder.newClient()) {
+ Book book = new Book("Test Book title", "Test Book author", "Test Book isbn", 10.0);
+
+ Response response = client
+ .target(URI.create("http://localhost:8080/"))
+ .path("/api/books")
+ .request(MediaType.APPLICATION_JSON)
+ .post(Entity.entity(book, MediaType.APPLICATION_JSON));
+
+ assertEquals(201, response.getStatus());
+ assertEquals("http://localhost:8080/api/books/1", response.getLocation().toString());
+ }
+ }
+
+ @Test
+ @Order(3)
+ public void list() {
+ try (Client client = ClientBuilder.newClient()) {
+ Response response = client
+ .target(URI.create("http://localhost:8080/"))
+ .path("/api/books")
+ .request(MediaType.APPLICATION_JSON)
+ .get();
+
+ assertEquals(200, response.getStatus());
+
+ List books = response.readEntity(new GenericType<>() {
+ });
+ assertEquals(1, books.size());
+
+ Book book = books.get(0);
+ assertEquals("Test Book title", book.getTitle());
+ assertEquals("Test Book author", book.getAuthor());
+ assertEquals("Test Book isbn", book.getIsbn());
+ assertEquals(10.0, book.getPrice());
+ }
+ }
+
+ @Test
+ @Order(4)
+ public void update() {
+ try (Client client = ClientBuilder.newClient()) {
+ Book book = new Book("Test Book title updated", "Test Book author updated", "Test Book isbn updated", 99.9);
+
+ Response response = client
+ .target(URI.create("http://localhost:8080/"))
+ .path("/api/books/1")
+ .request(MediaType.APPLICATION_JSON)
+ .put(Entity.entity(book, MediaType.APPLICATION_JSON));
+
+ assertEquals(200, response.getStatus());
+
+ response = client
+ .target(URI.create("http://localhost:8080/"))
+ .path("/api/books/1")
+ .request(MediaType.APPLICATION_JSON)
+ .get();
+
+ Book updated = response.readEntity(new GenericType<>() {
+ });
+
+ assertEquals("Test Book title updated", updated.getTitle());
+ assertEquals("Test Book author updated", updated.getAuthor());
+ assertEquals("Test Book isbn updated", updated.getIsbn());
+ assertEquals(99.9, updated.getPrice());
+ }
+ }
+
+ @Test
+ @Order(5)
+ public void delete() {
+ try (Client client = ClientBuilder.newClient()) {
+ Response response = client
+ .target(URI.create("http://localhost:8080/"))
+ .path("/api/books/1")
+ .request(MediaType.APPLICATION_JSON)
+ .delete();
+
+ assertEquals(204, response.getStatus());
+
+ response = client
+ .target(URI.create("http://localhost:8080/"))
+ .path("/api/books/1")
+ .request(MediaType.APPLICATION_JSON)
+ .get();
+
+ assertEquals(404, response.getStatus());
+ }
+ }
+}
+----
+
+=== Run the tests
+You can run the tests using the following command:
+
+[source,bash,subs="normal"]
+----
+export POSTGRESQL_USER={postgre-sql-user}
+export POSTGRESQL_PASSWORD={postgre-sql-password}
+export POSTGRESQL_DATABASE={postgre-sql-database}
+
+mvn clean verify
+----
+
+In this guide we have reused the same database instance for running the application and for the test cases. If you want to use a different instance for test cases, you have to adapt the values of the environment variables accordingly.
+
+=== Stop the database
+
+Finally, to stop the PostgreSQL database, press `Ctrl+C` in the terminal where the container is running.
+
+
+// Always keep a what's next? section to let the user know what could be achieved next
+== What's next?
+
+In this guide we have learnt how to configure a WildFly server to access to a PostgreSQL database and how to easily trim the server capabilities using WildFly Glow. Seamlessly, you can adapt the same application to use other databases by changing the Galleon Layers used by the WildFly server.
+You can learn more about how to configure WildFly for other databases by looking at the https://github.com/wildfly-extras/wildfly-datasources-galleon-pack[WildFly Datasource Galleon Pack] documentation and https://docs.wildfly.org/32/Glow_Guide.html[WildFly Glow Guide].
+
+// Always add this section last to link to any relevant content
+[[references]]
+== References
+
+* https://docs.wildfly.org/32/Admin_Guide.html#DataSource[WildFly Datasource Subsystem]
+* https://github.com/wildfly-extras/wildfly-datasources-galleon-pack[WildFly Datasource Galleon Pack]
+* https://docs.wildfly.org/32/Glow_Guide.html[WildFly Glow Guide].