Webapp deployment

A Java Web Application (webapp) is a collection of servlets, other Java classes, static resources (such as HTML pages), other resources, and meta information that describes the webapp bundled together.

The Java webapp in DX Cloud has a typical structure.

Webapp structure example
custom
├── .gitignore
├── .gitlab-ci.yml (1)
├── .m2
│   └── settings.xml
├── README.md
├── custom-webapp
│   ├── Dockerfile
│   ├── pom.xml
│   ├── src
│   └── target
├── pom.xml
└── values.yml (2)
1 The .gitlab-ci.yml file ensures your development changes are automatically picked up.
2 The values.yml file ensures your DX Cloud application is deployed to the your cluster.

The pipeline

When DX Cloud is set up with GitLab, changes in your project automatically trigger your pipeline via what is configured in your .gitlab-ci.yml file.

This way, changes are picked up automatically and you don’t have to worry about it. However, the final deployment step is sometimes manual and you’ll need to make a deploy action to finish the process. This is typically done by clicking a button manually in GitLab.

gitlab manual deploy action

The .gitlab-ci.yml file

It’s important that you configure the .gitlab-ci.yml file correctly so that your development changes are picked up and deployed. If you are using a different CI/CD pipeline, you can use this file as a blueprint.

Magnolia automatically picks up the changes when using this approach.
.gitlab-ci.yml
# Use the latest Maven version

stages:
  - build
  - push
  - deploy

variables:
  MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
  MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"

# Build the Maven project.
build-magnolia: (1)
  image: maven:3.6-jdk-11-slim
  stage: build
  cache:
    key: "$CI_JOB_NAME"
    paths:
      - $CI_PROJECT_DIR/.m2/repository
  before_script:
    - mkdir -p $CI_PROJECT_DIR/.m2
  script:
    - mvn $MAVEN_CLI_OPTS package
    - ls -Fahl base-webapp/target
  artifacts:
    expire_in: 30 days
    paths:
      - base-webapp/target/*.war

# Build docker images based on artifacts from the build stage.
push-docker-image: (2)
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  stage: push
  dependencies:
    - build-magnolia
  before_script:
    - export WEBAPP_IMAGE=${CI_REGISTRY_IMAGE}/magnolia-webapp
    - export GIT_TAG=$CI_COMMIT_SHORT_SHA (3)
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json (4)
  script:
    - cd base-webapp
    - /kaniko/executor --context . --dockerfile ./Dockerfile --destination "$WEBAPP_IMAGE:$GIT_TAG"

.deploy: (5)
  image: registry.gitlab.com/mironet/helm-kubectl-gomplate:v0.0.5
  stage: deploy
  before_script:
    - export GIT_TAG=$CI_COMMIT_SHORT_SHA
    - helm repo add mironet https://charts.mirohost.ch/
    - export HELM_CHART_VERSION=1.16.0
    - export KUBECONFIG=$KUBE_CONFIG (6)
    - chmod 600 $KUBE_CONFIG (6)

deploy-dev: (7)
  extends: .deploy
  script:
    - export DEPLOYMENT=dev
    - export LE_ENVIRONMENT=letsencrypt-prod
    - cat values.yml | gomplate > ${DEPLOYMENT}.yml
    - cat ${DEPLOYMENT}.yml
    - kubectl create namespace ${DEPLOYMENT} --dry-run=client -o yaml | kubectl annotate --local -f - field.cattle.io/projectId=`kubectl get namespace default --output="jsonpath={.metadata.annotations.field\.cattle\.io/projectId}"` -o yaml  | kubectl apply -f - (8)
    - |
      sleep 2
      until kubectl get namespace ${DEPLOYMENT}; do
        echo "Waiting for namespace ${DEPLOYMENT} to be created..."
        sleep 2
      done
    - helm upgrade -i ${DEPLOYMENT} mironet/magnolia-helm --version ${HELM_CHART_VERSION} -f ${DEPLOYMENT}.yml -n ${DEPLOYMENT} (9)
    - kubectl -n default get secret gitlab -o json | jq 'del(.metadata.annotations,.metadata.labels,.metadata.namespace,.metadata.resourceVersion,.metadata.uid,.metadata.namespace,.metadata.creationTimestamp)' | kubectl apply -n ${DEPLOYMENT}  -f - (10)
    - kubectl -n default get secret s3-backup-key -o json | jq 'del(.metadata.annotations,.metadata.labels,.metadata.namespace,.metadata.resourceVersion,.metadata.uid,.metadata.namespace,.metadata.creationTimestamp)' | kubectl apply -n ${DEPLOYMENT}  -f - (10)
  environment:
    name: dev (11)
  when: manual (12)

deploy-uat: (7)
  extends: .deploy
  script:
    - export DEPLOYMENT=uat
    - export LE_ENVIRONMENT=letsencrypt-prod
    - cat values.yml | gomplate > ${DEPLOYMENT}.yml
    - cat ${DEPLOYMENT}.yml
    - kubectl create namespace ${DEPLOYMENT} --dry-run=client -o yaml | kubectl annotate --local -f - field.cattle.io/projectId=`kubectl get namespace default --output="jsonpath={.metadata.annotations.field\.cattle\.io/projectId}"` -o yaml  | kubectl apply -f - (8)
    - |
      sleep 2
      until kubectl get namespace ${DEPLOYMENT}; do
        echo "Waiting for namespace ${DEPLOYMENT} to be created..."
        sleep 2
      done
    - helm upgrade -i ${DEPLOYMENT} mironet/magnolia-helm --version ${HELM_CHART_VERSION} -f ${DEPLOYMENT}.yml -n ${DEPLOYMENT} (9)
    - kubectl -n default get secret gitlab -o json | jq 'del(.metadata.annotations,.metadata.labels,.metadata.namespace,.metadata.resourceVersion,.metadata.uid,.metadata.namespace,.metadata.creationTimestamp)' | kubectl apply -n ${DEPLOYMENT}  -f - (10)
    - kubectl -n default get secret s3-backup-key -o json | jq 'del(.metadata.annotations,.metadata.labels,.metadata.namespace,.metadata.resourceVersion,.metadata.uid,.metadata.namespace,.metadata.creationTimestamp)' | kubectl apply -n ${DEPLOYMENT}  -f - (10)
  environment:
    name: dev (11)
  when: manual (12)
1 In the build-magnolia stage, the web app is built using maven, as with any Magnolia project. If using Magnolia 6.3, you need Java 17 under build-magnolia.image.
build-magnolia:
  image: maven:3.8-openjdk-17-slim
2 In the push-docker-image stage, the Docker image is built and pushed to the Docker registry (in this case the GitLab registry), using the Dockerfile located in the webapp folder.
3 The GIT_TAG is used to set the tag for the created Docker image.
4 The environment variables are set automatically by GitLab if the GitLab registry is used for the project.

We recommend that you use GitLab.

env variables
  • $CI_REGISTRY_USER

  • $CI_REGISTRY_PASSWORD

  • $CI_REGISTRY

5 The general deployment stage defines the helm chart repo and the version of the Helm chart to be used in the actual deployments.
6 The KUBE_CONFIG CI/CD variable should be defined as type File and hold KubeConfig of the cluster the deployment should go to. The same variable can be defined in different environment scopes (see 11). The chmod command changes the access to the file to avoid warnings.
7 The actual deployment stages define the namespace and prefix for the deployment. These stages can be duplicated for different namespaces (so that deployments can run in parallel on the cluster) and for different clusters (see 11).
8 This commands creates a namespace for the deployment and adds to the Rancher default project. A loop is integrated to ensure that the namespace exists before continuing. If the namespace already exists, the command is executed without errors.
9 Helm is using the Mironet Helm Chart to deploy the Magnolia App and the corresponding databases using the provided values.yml file (see Helm Values) to the defined namespace.
10 The needed secrets are copied over from the default namespace to the newly created namespace.
11 The environment name corresponds to the environment scope (dev or prod) defined in the Deployments section. In different environments the same variable names can be used.
12 The deployment must be triggered manually.

The values.yml file

The values.yml file will hold the configuration used by the Magnolia Helm Chart in the process of deploying the application to the cluster. Properties like DEPLOYMENT must be the same as in the .gitlab-ci.yml file.

Here, we provide you with a template values.yml file to get you started.

For more details on values, see our DX Cloud Helm Values reference page.
Prod vs non-prod

Typically, you will have a values.yml file for both prod and non-prod as the values will be different depending on if the deployment is intended for testing or production. We encourage you to have separate files for this purpose.

Example:

  • test = values.yml

  • prod = values-prod.yml

Cert-manager
ingress:
  enabled: true
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/proxy-body-size: 512m
    cert-manager.io/cluster-issuer: "letsencrypt-prod" (1)
1 The cert-manager is automatically created by us. However, you can use your own. If you choose to do this, please contact the DX Cloud Helpdesk.

Sample file

values.yml
ingress:
  enabled: true
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/proxy-body-size: 512m
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
  #
  # run author and public in different contexts and use the same domain name
  # only one of these hosts/tls sections may be active
  #
  #  hosts:
  #    - host: {{ .Env.DEPLOYMENT }}.<realm>.magnolia-platform.com (1) (2)
  #      paths:
  #        - path: /
  #          instance: public
  #        - path: /author
  #          instance: author
  #  tls:
  #  - hosts:
  #    - {{ .Env.DEPLOYMENT }}.<realm>.magnolia-platform.com (1) (2)
  #    secretName: {{ .Env.DEPLOYMENT }}-<realm>-magnolia-platform-com (1) (2)
  #
  # run author and public in ROOT context and use different domain names
  # only one of these hosts/tls sections may be active
  #
#  hosts:
  - host: {{ .Env.DEPLOYMENT }}.author.<realm>.magnolia-platform.com
    paths:
    - path: /
      instance: author
  - host: {{ .Env.DEPLOYMENT }}.public.<realm>.magnolia-platform.com
    paths:
    - path: /
      instance: public
#  - host: {{ .Env.DEPLOYMENT }}.frontend-author.<realm>.magnolia-platform.com
#    paths:
#    - path: /
#      instance: frontend-author
#  - host: {{ .Env.DEPLOYMENT }}.frontend-public.<realm>.magnolia-platform.com
#    paths:
#    - path: /
#      instance: frontend-public
  tls:
  - hosts:
    - {{ .Env.DEPLOYMENT }}.author.<realm>.magnolia-platform.com
    - {{ .Env.DEPLOYMENT }}.public.<realm>.magnolia-platform.com
#    - {{ .Env.DEPLOYMENT }}.frontend-author.<realm>.magnolia-platform.com
#    - {{ .Env.DEPLOYMENT }}.frontend-public.<realm>.magnolia-platform.com
    secretName: {{ .Env.DEPLOYMENT }}-<realm>-magnolia-platform-com
image:
  pullSecrets: (3)
    - name: gitlab
  pullPolicy: Always
magnoliaAuthor:
  enabled: true
  restartPolicy: Always
  redeploy: true (4)
  bootstrap:
    password: "<password>" (5)
  activation:
    useExistingSecret: False (6)
  #
  # run author and public in different contexts and use the same domain name
  # only one of these contextPath values may be active
  #
#  contextPath: /author
#  base_url: https://{{ .Env.DEPLOYMENT }}.<realm>.magnolia-platform.io/author
  #
  # run author and public in ROOT context and use different domain names
  # only one of these contextPath values may be active
  #
  contextPath: /
  base_url: https://{{ .Env.DEPLOYMENT }}.author.<realm>.magnolia-platform.io/
  sameSiteCookies: strict
  webarchive:
    repository: {{ .Env.CI_REGISTRY_IMAGE }}/magnolia-webapp
    tag: {{ .Env.GIT_TAG | quote }}
  env:
    - name: instance
      value: "author"
    - name: deployment
      value: {{ .Env.DEPLOYMENT }}
    - name: magnolia.superuser.enabled
      value: "true"
    - name: magnolia.superuser.password
      value: "<password>" (5)
    - name: magnolia.bootstrap.license.owner
      value: "[replace with email]" (7)
    - name: magnolia.bootstrap.license.key
      value: "[replace with key]" (7)
  setenv:
    memory:
      maxPercentage: 80 (8)
  resources:
    requests:
      memory: 4Gi (9)
    limits:
      memory: 4Gi (9)
  livenessProbe:
    enabled: true
    path: "/.rest/status"

  db:
    tag: 15-alpine (10)
    persistence:
      size: "10Gi" (11)
    contentsync:
      enabled: true
    restore:
      enabled: False
    backup:
      enabled: True (12)
      env:
        - name: MGNLBACKUP_USE_PG_WAL
          value: "true"
        - name: MGNLBACKUP_SYNC_DIR
          value: "/archive"
        - name: MGNLBACKUP_NO_STDOUT
          value: "true"
        - name: MGNLBACKUP_LOGLEVEL
          value: "debug"
        - name: MGNLBACKUP_BUCKET
          value: "<realm>-backup-bucket" (2)
        - name: MGNLBACKUP_PREFIX
          value: "{{ .Env.DEPLOYMENT }}/author"
        - name: MGNLBACKUP_CRON
          value: "0 3 * * *" (13)
        - name: MGNLBACKUP_KEEPDAYS
          value: "30"
        - name: MGNLBACKUP_TAGS_RELEASE
          value: {{ .Env.DEPLOYMENT }}
        #
        # Choose backup location based on cloud provider of the cluster
        #
        # Backup to S3
        - name: MGNLBACKUP_S3_ENDPOINT (14)
          value: "s3.eu-central-1.amazonaws.com"
        - name: MGNLBACKUP_S3_REGION
          value: "eu-central-1"
        - name: MGNLBACKUP_S3_ACCESSKEY
          valueFrom:
            secretKeyRef:
              name: s3-backup-key
              key: accesskey
        - name: MGNLBACKUP_S3_SECRETKEY
          valueFrom:
            secretKeyRef:
              name: s3-backup-key
              key: secretkey
        # Backup to Azure storage
#        - name: MGNLBACKUP_AZ_ACCOUNT_NAME
#          valueFrom:
#            secretKeyRef:
#              name: az-backup-key
#              key: AccountName
#        - name: MGNLBACKUP_AZ_ACCOUNT_KEY
#          valueFrom:
#            secretKeyRef:
#              name: az-backup-key
#              key: AccountKey

magnoliaPublic:
  enabled: true
  replicas: 1 (15)
  restartPolicy: Always
  bootstrap:
    password: <password>" (5)
  activation:
    useExistingSecret: False (6)
  contextPath: /
  #
  # run author and public in different contexts and use the same domain name
  # only one of these base_url values may be active
  #
#  base_url: https://{{ .Env.DEPLOYMENT }}.<realm>.magnolia-platform.io/
  #
  # run author and public in ROOT context and use different domain names
  # only one of these base_url values may be active
  #
  base_url: https://{{ .Env.DEPLOYMENT }}.public.<realm>.magnolia-platform.io/
  sameSiteCookies: strict
  webarchive:
    repository: {{ .Env.CI_REGISTRY_IMAGE }}/magnolia-webapp
    tag: {{ .Env.GIT_TAG | quote }}
  env:
    - name: instance
      value: "public"
    - name: deployment
      value: {{ .Env.DEPLOYMENT }}
    - name: magnolia.superuser.enabled
      value: "true"
    - name: magnolia.superuser.password
      value: "<password>" (5)
    - name: magnolia.bootstrap.license.owner
      value: "[replace with email]" (7)
    - name: magnolia.bootstrap.license.key
      value: "[replace with key]" (7)
  setenv:
    memory:
      maxPercentage: 80 (8)
  resources:
    requests:
      memory: 4Gi (9)
    limits:
      memory: 4Gi (9)
  livenessProbe:
    enabled: true
    path: "/.rest/status"
  db:
    tag: 15-alpine
    persistence:
      size: "10Gi" (11)
    contentsync:
      enabled: true
    restore:
      enabled: False
    backup:
      enabled: True (12)
      env:
        - name: MGNLBACKUP_USE_PG_WAL
          value: "true"
        - name: MGNLBACKUP_SYNC_DIR
          value: "/archive"
        - name: MGNLBACKUP_NO_STDOUT
          value: "true"
        - name: MGNLBACKUP_LOGLEVEL
          value: "debug"
        - name: MGNLBACKUP_HERITAGE
          value: "magnolia-backup"
        - name: MGNLBACKUP_BUCKET
          value: "<realm>-backup-bucket" (2)
        - name: MGNLBACKUP_PREFIX
          value: "{{ .Env.DEPLOYMENT }}/public"
        - name: MGNLBACKUP_CRON
          value: "0 3 * * *" (13)
        - name: MGNLBACKUP_KEEPDAYS
          value: "30"
        - name: MGNLBACKUP_TAGS_RELEASE
          value: {{ .Env.DEPLOYMENT }}
        #
        # Choose backup location based on cloud provider of the cluster
        #
        # Backup to S3
        - name: MGNLBACKUP_S3_ENDPOINT (14)
          value: "s3.eu-central-1.amazonaws.com"
        - name: MGNLBACKUP_S3_REGION
          value: "eu-central-1"
        - name: MGNLBACKUP_S3_ACCESSKEY
          valueFrom:
            secretKeyRef:
              name: s3-backup-key
              key: accesskey
        - name: MGNLBACKUP_S3_SECRETKEY
          valueFrom:
            secretKeyRef:
              name: s3-backup-key
              key: secretkey
        # Backup to Azure storage
#        - name: MGNLBACKUP_AZ_ACCOUNT_NAME
#          valueFrom:
#            secretKeyRef:
#              name: az-backup-key
#              key: AccountName
#        - name: MGNLBACKUP_AZ_ACCOUNT_KEY
#          valueFrom:
#            secretKeyRef:
#              name: az-backup-key
#              key: AccountKey


# Additional jars which should be loaded into tomcat can be specified here.
jars:
  - name: jmx-exporter
    repository: registry.gitlab.com/mironet/magnolia-jar
    tag: jmx_prometheus_javaagent-0.13.0
    env:
      - name: INIT_DEST
        value: /extraLibs/
    initScript: /init.sh
# # Required Toleration and Affinity for "Prod" Deployments, if running on PaaS Basic Clusters
#
# # Add Toleration
# tolerations:
#   - key: dedicated
#     operator: Equal
#     value: prod
#     effect: PreferNoSchedule
# # Hard Node Affinity
# nodeAffinity:
#   preferredDuringSchedulingIgnoredDuringExecution:
#     nodeSelectorTerms:
#       - matchExpressions:
#           - key: dedicated
#             operator: In
#             values:
#               - prod
1 The {{ .Env.DEPLOYMENT }} value should be set by the build pipeline and should be the same as used in the RELEASE value in the Helm call.
A wildcard DNS entry for <realm>.magnolia-platform.com `would support any valid value for `{{ .Env.DEPLOYMENT }} and create a corresponding SSL certificate.
2 The <realm> name is generated by Magnolia when we create your cluster. You can set this value as shortname while creating the Magnolia project using the Cloud Archetype
This is typically your company name.
3 Specify the pullSecrets.
if the webarchive image is located in a private registry, see [Use Docker secret].
4 Boolean specifying if there is an automatic redeployment triggered by changes.
defaults
  • magnoliaAuthor = false

  • magnoliaPublic = true

If set to true the instance is restarted even if the tag of the webarchive was not changed between deployments.

5 Specify the environment password.
This must be the same in all places for the bootstrapping container to work correctly.
6 The activation key should be handled by Magnolia.
7 These 2 values must be replaced with the license info for this project, provided by Magnolia.
8 Specify the maximum percentage memory that is reserved for the tomcat container.

Default = 80 / Recommended for production = 60

9 Sets your memory for requests and limits.
4GB is typically sufficient for Magnolia CMS.
10 Specify the PostgreSQL version (Default as per Helm Chart: 11.5-alpine)
11 Sets the volume size of the database.
10Gi is typically sufficient for a dev environment, 100Gi for production.
12 Defines that backups of the database are taken and stored to an S3 bucket or Azure storage (depending on your cloud provider) provisioned by Magnolia. The value of the MGNLBACKUP_BUCKET will be provided to you by Magnolia
Every transaction is written, which allows point-in-time recovery, see PostGres Continuous Archiving for more details.
13 Defines the time when the full backup of the database takes place, in CRON syntax.
The example shows every day at 3am.
14 The values for the endpoints and credential secrets will be provided by Magnolia, comment either the _S3_ or _AZ_ values, depending on your cloud provider
15 The replicas parameter used on magnoliaPublic defines how many public instances are created. If not defined, 1 public instance is created.

Clone Secrets with Rancher 2.6

Before Rancher 2.6, you could create a ("project-unbound") secret in the All namespace. Rancher made those secrets available to all projects and namespaces.

This feature was deprecated deprecated with Rancher 2.6 for a good reason, as it breaks recommended namespace isolation.

To achieve this same goal:

  1. Copy the secrets using the Rancher UI:

  2. Then clone the desired secret and into the target namespace (ie. namespace uat).

    clonesecret 1blur

    clonesecret 2blur

values.yml

This is good to know when needed in the values.yml file under the useExistingSecret field as it will provide the activation key secret for the target environment.

...
magnoliaAuthor:
  replicas: 1
  restartPolicy: Always
  redeploy: true
  contextPath: /author
  webarchive:
    repository: {{ .Env.CI_REGISTRY_IMAGE }}/magnolia-webapp
    tag: "{{ .Env.GIT_TAG | quote }}"
  bootstrap:
    password: "<password>"
  activation:
    useExistingSecret: True (1)
    secret:
      name: activation-key
      key: activation-secret
...
magnoliaPublic:
  replicas: 2
  restartPolicy: Always
  contextPath: /
  webarchive:
    repository: {{ .Env.CI_REGISTRY_IMAGE }}/magnolia-webapp
    tag: "{{ .Env.GIT_TAG | quote }}"
  bootstrap:
    password: "<password>"
  activation:
    useExistingSecret: True (1)
    secret:
      name: activation-key
      key: activation-secret
...
1 The activation key is handled by the bootstrapper container. This keeps magnoliaAuthor and magnoliaPublic in sync.
Feedback

PaaS

×

Location

This widget lets you know where you are on the docs site.

You are currently perusing through the Magnolia PaaS docs.

Main doc sections

DX Core Headless PaaS Legacy Cloud Incubator modules