Tutorial: Connect to PostgreSQL Database from a Java Quarkus Container App without secrets using a managed identity
Azure Container Apps provides a managed identity for your app, which is a turn-key solution for securing access to Azure Database for PostgreSQL and other Azure services. Managed identities in Container Apps make your app more secure by eliminating secrets from your app, such as credentials in the environment variables.
This tutorial walks you through the process of building, configuring, deploying, and scaling Java container apps on Azure. At the end of this tutorial, you'll have a Quarkus application storing data in a PostgreSQL database with a managed identity running on Container Apps.
What you will learn:
- Configure a Quarkus app to authenticate using Microsoft Entra ID with a PostgreSQL Database.
- Create an Azure container registry and push a Java app image to it.
- Create a Container App in Azure.
- Create a PostgreSQL database in Azure.
- Connect to a PostgreSQL Database with managed identity using Service Connector.
If you don't have an Azure subscription, create an Azure free account before you begin.
1. Prerequisites
2. Create a container registry
Create a resource group with the az group create command. An Azure resource group is a logical container into which Azure resources are deployed and managed.
The following example creates a resource group named myResourceGroup
in the East US Azure region.
RESOURCE_GROUP="myResourceGroup"
LOCATION="eastus"
az group create --name $RESOURCE_GROUP --location $LOCATION
Create an Azure container registry instance using the az acr create command and retrieve its login server using the az acr show command. The registry name must be unique within Azure and contain 5-50 alphanumeric characters. All letters must be specified in lower case. In the following example, mycontainerregistry007
is used. Update this to a unique value.
REGISTRY_NAME=mycontainerregistry007
az acr create \
--resource-group $RESOURCE_GROUP \
--name $REGISTRY_NAME \
--sku Basic
REGISTRY_SERVER=$(az acr show \
--name $REGISTRY_NAME \
--query 'loginServer' \
--output tsv | tr -d '\r')
3. Clone the sample app and prepare the container image
This tutorial uses a sample Fruits list app with a web UI that calls a Quarkus REST API backed by Azure Database for PostgreSQL. The code for the app is available on GitHub. To learn more about writing Java apps using Quarkus and PostgreSQL, see the Quarkus Hibernate ORM with Panache Guide and the Quarkus Datasource Guide.
Run the following commands in your terminal to clone the sample repo and set up the sample app environment.
git clone https://github.com/quarkusio/quarkus-quickstarts
cd quarkus-quickstarts/hibernate-orm-panache-quickstart
Modify your project
Add the required dependencies to your project's BOM file.
<dependency> <groupId>com.azure</groupId> <artifactId>azure-identity-extensions</artifactId> <version>1.1.20</version> </dependency>
Configure the Quarkus app properties.
The Quarkus configuration is located in the src/main/resources/application.properties file. Open this file in your editor, and observe several default properties. The properties prefixed with
%prod
are only used when the application is built and deployed, for example when deployed to Azure App Service. When the application runs locally,%prod
properties are ignored. Similarly,%dev
properties are used in Quarkus' Live Coding / Dev mode, and%test
properties are used during continuous testing.Delete the existing content in application.properties and replace with the following to configure the database for dev, test, and production modes:
quarkus.hibernate-orm.database.generation=drop-and-create quarkus.datasource.db-kind=postgresql quarkus.datasource.jdbc.max-size=8 quarkus.datasource.jdbc.min-size=2 quarkus.hibernate-orm.log.sql=true quarkus.hibernate-orm.sql-load-script=import.sql quarkus.datasource.jdbc.acquisition-timeout = 10 %dev.quarkus.datasource.username=${CURRENT_USERNAME} %dev.quarkus.datasource.jdbc.url=jdbc:postgresql://${AZURE_POSTGRESQL_HOST}:${AZURE_POSTGRESQL_PORT}/${AZURE_POSTGRESQL_DATABASE}?\ authenticationPluginClassName=com.azure.identity.extensions.jdbc.postgresql.AzurePostgresqlAuthenticationPlugin\ &sslmode=require %prod.quarkus.datasource.username=${AZURE_POSTGRESQL_USERNAME} %prod.quarkus.datasource.jdbc.url=jdbc:postgresql://${AZURE_POSTGRESQL_HOST}:${AZURE_POSTGRESQL_PORT}/${AZURE_POSTGRESQL_DATABASE}?\ authenticationPluginClassName=com.azure.identity.extensions.jdbc.postgresql.AzurePostgresqlAuthenticationPlugin\ &sslmode=require %dev.quarkus.class-loading.parent-first-artifacts=com.azure:azure-core::jar,\ com.azure:azure-core-http-netty::jar,\ io.projectreactor.netty:reactor-netty-core::jar,\ io.projectreactor.netty:reactor-netty-http::jar,\ io.netty:netty-resolver-dns::jar,\ io.netty:netty-codec::jar,\ io.netty:netty-codec-http::jar,\ io.netty:netty-codec-http2::jar,\ io.netty:netty-handler::jar,\ io.netty:netty-resolver::jar,\ io.netty:netty-common::jar,\ io.netty:netty-transport::jar,\ io.netty:netty-buffer::jar,\ com.azure:azure-identity::jar,\ com.azure:azure-identity-extensions::jar,\ com.fasterxml.jackson.core:jackson-core::jar,\ com.fasterxml.jackson.core:jackson-annotations::jar,\ com.fasterxml.jackson.core:jackson-databind::jar,\ com.fasterxml.jackson.dataformat:jackson-dataformat-xml::jar,\ com.fasterxml.jackson.datatype:jackson-datatype-jsr310::jar,\ org.reactivestreams:reactive-streams::jar,\ io.projectreactor:reactor-core::jar,\ com.microsoft.azure:msal4j::jar,\ com.microsoft.azure:msal4j-persistence-extension::jar,\ org.codehaus.woodstox:stax2-api::jar,\ com.fasterxml.woodstox:woodstox-core::jar,\ com.nimbusds:oauth2-oidc-sdk::jar,\ com.nimbusds:content-type::jar,\ com.nimbusds:nimbus-jose-jwt::jar,\ net.minidev:json-smart::jar,\ net.minidev:accessors-smart::jar,\ io.netty:netty-transport-native-unix-common::jar,\ net.java.dev.jna:jna::jar
Build and push a Docker image to the container registry
Build the container image.
Run the following command to build the Quarkus app image. You must tag it with the fully qualified name of your registry login server.
CONTAINER_IMAGE=${REGISTRY_SERVER}/quarkus-postgres-passwordless-app:v1 mvn quarkus:add-extension -Dextensions="container-image-jib" mvn clean package -Dquarkus.container-image.build=true -Dquarkus.container-image.image=${CONTAINER_IMAGE}
Log in to the registry.
Before pushing container images, you must log in to the registry. To do so, use the [az acr login][az-acr-login] command.
az acr login --name $REGISTRY_NAME
The command returns a
Login Succeeded
message once completed.Push the image to the registry.
Use [docker push][docker-push] to push the image to the registry instance. This example creates the
quarkus-postgres-passwordless-app
repository, containing thequarkus-postgres-passwordless-app:v1
image.docker push $CONTAINER_IMAGE
4. Create a Container App on Azure
Create a Container Apps instance by running the following command. Make sure you replace the value of the environment variables with the actual name and location you want to use.
CONTAINERAPPS_ENVIRONMENT="my-environment" az containerapp env create \ --resource-group $RESOURCE_GROUP \ --name $CONTAINERAPPS_ENVIRONMENT \ --location $LOCATION
Create a container app with your app image by running the following command:
APP_NAME=my-container-app az containerapp create \ --resource-group $RESOURCE_GROUP \ --name $APP_NAME \ --image $CONTAINER_IMAGE \ --environment $CONTAINERAPPS_ENVIRONMENT \ --registry-server $REGISTRY_SERVER \ --registry-identity system \ --ingress 'external' \ --target-port 8080 \ --min-replicas 1
Note
The options
--registry-username
and--registry-password
are still supported but aren't recommended because using the identity system is more secure.
5. Create and connect a PostgreSQL database with identity connectivity
Next, create a PostgreSQL Database and configure your container app to connect to a PostgreSQL Database with a system-assigned managed identity. The Quarkus app will connect to this database and store its data when running, persisting the application state no matter where you run the application.
Create the database service.
DB_SERVER_NAME='msdocs-quarkus-postgres-webapp-db' az postgres flexible-server create \ --resource-group $RESOURCE_GROUP \ --name $DB_SERVER_NAME \ --location $LOCATION \ --public-access None \ --sku-name Standard_B1ms \ --tier Burstable \ --active-directory-auth Enabled
Note
The options
--admin-user
and--admin-password
are still supported but aren't recommended because using the identity system is more secure.The following parameters are used in the above Azure CLI command:
- resource-group → Use the same resource group name in which you created the web app - for example,
msdocs-quarkus-postgres-webapp-rg
. - name → The PostgreSQL database server name. This name must be unique across all Azure (the server endpoint becomes
https://<name>.postgres.database.azure.com
). Allowed characters areA
-Z
,0
-9
, and-
. A good pattern is to use a combination of your company name and server identifier. (msdocs-quarkus-postgres-webapp-db
) - location → Use the same location used for the web app. Change to a different location if it doesn't work.
- public-access →
None
which sets the server in public access mode with no firewall rules. Rules will be created in a later step. - sku-name → The name of the pricing tier and compute configuration - for example,
Standard_B1ms
. For more information, see Azure Database for PostgreSQL pricing. - tier → The compute tier of the server. For more information, see Azure Database for PostgreSQL pricing.
- active-directory-auth →
Enabled
to enable Microsoft Entra authentication.
- resource-group → Use the same resource group name in which you created the web app - for example,
Create a database named
fruits
within the PostgreSQL service with this command:DB_NAME=fruits az postgres flexible-server db create \ --resource-group $RESOURCE_GROUP \ --server-name $DB_SERVER_NAME \ --database-name $DB_NAME
Install the Service Connector passwordless extension for the Azure CLI:
az extension add --name serviceconnector-passwordless --upgrade --allow-preview true
Connect the database to the container app with a system-assigned managed identity, using the connection command.
az containerapp connection create postgres-flexible \ --resource-group $RESOURCE_GROUP \ --name $APP_NAME \ --target-resource-group $RESOURCE_GROUP \ --server $DB_SERVER_NAME \ --database $DB_NAME \ --system-identity \ --container $APP_NAME
6. Review your changes
You can find the application URL(FQDN) by using the following command:
echo https://$(az containerapp show \
--name $APP_NAME \
--resource-group $RESOURCE_GROUP \
--query properties.configuration.ingress.fqdn \
--output tsv)
When the new webpage shows your list of fruits, your app is connecting to the database using the managed identity. You should now be able to edit fruit list as before.
Clean up resources
In the preceding steps, you created Azure resources in a resource group. If you don't expect to need these resources in the future, delete the resource group by running the following command in the Cloud Shell:
az group delete --name myResourceGroup
This command may take a minute to run.
Next steps
Learn more about running Java apps on Azure in the developer guide.