Overview

In this article, we will start the latest version (25.3.0) of Alfresco Governance Services Community Edition (hereinafter AGS) with Docker and experience the entire records management lifecycle using the REST API.

Specifically, we assume the following business scenario.

Scenario: Contract Management

  1. The business department creates and registers a contract
  2. The records manager declares it as a record and classifies it in the file plan
  3. A retention schedule is configured
  4. After the contract ends, a cutoff (active to inactive) is executed
  5. After the retention period (3 years) has elapsed, disposal is performed
  6. If litigation arises, a hold (freeze) is placed to stop disposal

The following builds on the previous article and introduces setup procedures and API usage for the latest version.

Environment

  • acs-deployment: v10.2.0 (Released February 2026)
  • Alfresco Governance Repository Community: 25.3.0
  • Alfresco Governance Share Community: 25.3.0
  • Alfresco Search Services: 2.0.17
  • Traefik: 3.6
  • PostgreSQL: 16.5

Setup

Cloning the Repository

git clone https://github.com/Alfresco/acs-deployment
cd acs-deployment
git checkout v10.2.0
cd docker-compose

Creating the Compose File

Create a compose file for Governance Services based on community-compose.yaml. The changes are the following three:

1. Image Replacement

ServiceBeforeAfter
alfrescoalfresco/alfresco-content-repository-community:25.3.0alfresco/alfresco-governance-repository-community:25.3.0
sharealfresco/alfresco-share:25.3.0alfresco/alfresco-governance-share-community:25.3.0

2. Authentication Ticket Timeout Countermeasure (described later)

3. DB Connection Pool Validation Settings (described later)

Below is the compose file that was created.

services:
  alfresco:
    image: docker.io/alfresco/alfresco-governance-repository-community:25.3.0
    mem_limit: 1900m
    environment:
      JAVA_TOOL_OPTIONS: >-
        -Dencryption.keystore.type=JCEKS
        -Dencryption.cipherAlgorithm=DESede/CBC/PKCS5Padding
        -Dencryption.keyAlgorithm=DESede
        -Dencryption.keystore.location=/usr/local/tomcat/shared/classes/alfresco/extension/keystore/keystore
        -Dmetadata-keystore.password=mp6yc0UD9e
        -Dmetadata-keystore.aliases=metadata
        -Dmetadata-keystore.metadata.password=oKIWzVdEdA
        -Dmetadata-keystore.metadata.algorithm=DESede
      JAVA_OPTS: >-
        -Ddb.driver=org.postgresql.Driver
        -Ddb.username=alfresco
        -Ddb.password=alfresco
        -Ddb.url=jdbc:postgresql://postgres:5432/alfresco
        -Dsolr.host=solr6
        -Dsolr.port=8983
        -Dsolr.http.connection.timeout=1000
        -Dsolr.secureComms=secret
        -Dsolr.sharedSecret=secret
        -Dsolr.base.url=/solr
        -Dindex.subsystem.name=solr6
        -Dshare.host=localhost
        -Dshare.port=8080
        -Dalfresco.host=localhost
        -Dalfresco.port=8080
        -Dcsrf.filter.enabled=false
        -Daos.baseUrlOverwrite=http://localhost:8080/alfresco/aos
        -Dmessaging.broker.url="failover:(nio://activemq:61616)?timeout=3000&jms.useCompression=true"
        -Ddeployment.method=DOCKER_COMPOSE
        -DlocalTransform.core-aio.url=http://transform-core-aio:8090/
        -Dauthentication.ticket.ticketsExpire=true
        -Dauthentication.ticket.expiryMode=AFTER_INACTIVITY
        -Dauthentication.ticket.validDuration=PT8H
        -Dauthentication.ticket.useSingleTicketPerUser=false
        -Ddb.pool.validate.query=SELECT\ 1
        -Ddb.pool.evict.interval=600
        -XX:MinRAMPercentage=50
        -XX:MaxRAMPercentage=80
    healthcheck:
      test:
        [
          "CMD",
          "curl",
          "-f",
          "http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/probes/-ready-",
        ]
      interval: 30s
      timeout: 3s
      retries: 5
      start_period: 1m
    volumes:
      - /usr/local/tomcat/alf_data
    extends:
      file: commons/base.yaml
      service: alfresco
  transform-core-aio:
    image: alfresco/alfresco-transform-core-aio:5.3.0
    mem_limit: 1536m
    environment:
      JAVA_OPTS: >-
        -XX:MinRAMPercentage=50
        -XX:MaxRAMPercentage=80
    ports:
      - "8090:8090"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8090/ready"]
      interval: 20s
      timeout: 2s
      retries: 3
      start_period: 10s
    depends_on:
      activemq:
        condition: service_healthy
  share:
    image: docker.io/alfresco/alfresco-governance-share-community:25.3.0
    mem_limit: 1g
    environment:
      CSRF_FILTER_ORIGIN: http://localhost:8080
      CSRF_FILTER_REFERER: http://localhost:8080/share/.*
      REPO_HOST: "alfresco"
      REPO_PORT: "8080"
      JAVA_OPTS: >-
        -XX:MinRAMPercentage=50
        -XX:MaxRAMPercentage=80
        -Dalfresco.host=localhost
        -Dalfresco.port=8080
        -Dalfresco.context=alfresco
        -Dalfresco.protocol=http
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/share"]
      interval: 20s
      timeout: 2s
      retries: 3
      start_period: 15s
    depends_on:
      alfresco:
        condition: service_healthy
    extends:
      file: commons/base.yaml
      service: share
  postgres:
    image: postgres:16.5
    mem_limit: 512m
    environment:
      - POSTGRES_PASSWORD=alfresco
      - POSTGRES_USER=alfresco
      - POSTGRES_DB=alfresco
    command: postgres -c max_connections=300 -c log_min_messages=LOG
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $$POSTGRES_DB -U $$POSTGRES_USER"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 5s
  solr6:
    image: docker.io/alfresco/alfresco-search-services:2.0.17
    mem_limit: 2g
    environment:
      SOLR_ALFRESCO_HOST: "alfresco"
      SOLR_ALFRESCO_PORT: "8080"
      SOLR_SOLR_HOST: "solr6"
      SOLR_SOLR_PORT: "8983"
      SOLR_CREATE_ALFRESCO_DEFAULTS: "alfresco,archive"
      ALFRESCO_SECURE_COMMS: "secret"
      JAVA_TOOL_OPTIONS: >-
        -Dalfresco.secureComms.secret=secret
    ports:
      - "8083:8983"
  activemq:
    image: alfresco/alfresco-activemq:5.18-jre17-rockylinux8
    mem_limit: 1g
    ports:
      - "8161:8161"
      - "5672:5672"
      - "61616:61616"
      - "61613:61613"
    healthcheck:
      test:
        [
          "CMD",
          "/opt/activemq/bin/activemq",
          "query",
          "--objname",
          "type=Broker,brokerName=*,service=Health",
          "|",
          "grep",
          "Good",
        ]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 5s
  content-app:
    image: alfresco/alfresco-content-app:7.2.0
    mem_limit: 128m
    environment:
      APP_BASE_SHARE_URL: "http://localhost:8080/aca/#/preview/s"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/"]
      interval: 10s
      timeout: 1s
      retries: 3
      start_period: 1s
    extends:
      file: commons/base.yaml
      service: content-app
  control-center:
    image: quay.io/alfresco/alfresco-control-center:10.2.0
    mem_limit: 128m
    environment:
      APP_CONFIG_PROVIDER: "ECM"
      APP_CONFIG_AUTH_TYPE: "BASIC"
      BASE_PATH: ./
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/"]
      interval: 10s
      timeout: 1s
      retries: 3
      start_period: 1s
    extends:
      file: commons/base.yaml
      service: control-center
  proxy:
    extends:
      file: commons/base.yaml
      service: proxy

Startup

docker compose -f governance-community-compose.yaml up -d

After startup, verify that all containers are healthy.

docker compose -f governance-community-compose.yaml ps
NAME                                  STATUS
docker-compose-activemq-1             Up (healthy)
docker-compose-alfresco-1             Up (healthy)
docker-compose-content-app-1          Up (healthy)
docker-compose-control-center-1       Up (healthy)
docker-compose-postgres-1             Up (healthy)
docker-compose-proxy-1                Up (healthy)
docker-compose-share-1                Up (healthy)
docker-compose-solr6-1                Up
docker-compose-transform-core-aio-1   Up (healthy)

The following URLs are available for access. The default login credentials are admin / admin.

URLPurpose
http://localhost:8080/share/Alfresco Share (Governance Services UI)
http://localhost:8080/content-app/Alfresco Content App
http://localhost:8080/control-center/Alfresco Control Center
http://localhost:8080/alfresco/Alfresco Repository

The Alfresco Share login screen is displayed.

After logging in, the dashboard is shown. The Records Management site is displayed.

The system is also accessible from the Content App.

The Control Center allows management of users and groups.

Experiencing Records Management via REST API

From here, we will use the REST API to walk through the contract management business scenario.

All API commands can be executed with curl. Authentication uses Basic authentication (admin:admin).

1. Connection Verification

First, verify the API connection and the logged-in user.

curl -s -u admin:admin \
  "http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/people/-me-" \
  | python3 -m json.tool
{
    "entry": {
        "firstName": "Administrator",
        "capabilities": {
            "isGuest": false,
            "isAdmin": true,
            "isMutable": true
        },
        "displayName": "Administrator",
        "id": "admin",
        "enabled": true,
        "email": "admin@alfresco.com"
    }
}

2. Creating a Records Management Site

To use Governance Services features, you first need to create an RM site. Instead of the regular site creation API, use the GS-specific API.

!

compliance can be selected from STANDARD and DOD5015. DOD5015 is a mode that complies with the US Department of Defense records management standard. Here we use STANDARD.

3. Verifying the File Plan

When an RM site is created, a File Plan is automatically generated. The File Plan is the top-level structure for records management and serves as the root of the classification system.

The following structures are auto-generated within the File Plan:

Node TypeNameDescription
rma:recordCategoryContractsRecord category (created later)
rma:holdContainerHoldsHold (legal preservation) container
rma:transferContainerTransfersTransfer container
rma:unfiledRecordContainerUnfiled RecordsUnfiled records container

4. Creating a Record Category

Create a record category according to the business classification system. Here we create a “Contracts” category.

5. Creating a Record Folder

Create a record folder within the category.

6. Uploading a Document

Upload a contract document.

7. Declaring as Record

Declare the document as a record using the GS API.

8. Filing the Record (Classification)

File the record into the appropriate record folder.

9. Retention Schedule Configuration

A retention schedule defines the lifecycle of a record. It is set on a category and applies to the folders and records under it.

9-1. Creating a Retention Schedule

!

isRecordLevel specifies whether the retention schedule is applied at the record level or the folder level. Here we select folder level (false). For categories that already contain folders, the record level (true) cannot be set.

9-2. Adding Retention Actions

Add specific actions (steps) to the retention schedule. Here we use the Legacy API.

!

Adding retention actions may cause a 500 error with the v1 GS REST API due to issues with the period format. Using the Legacy API (/alfresco/s/api/...) ensures reliable operation.

The available retention actions are the following 5 types:

ActionDescription
cutoffCutoff (transition from active to inactive)
retainRetain (storage for a specified period)
transferTransfer (moving to another storage location)
accessionAccession (transfer to archive)
destroyDestroy

10. Completing the Record

To execute a cutoff, all records within the folder must be in “Complete” status.

!

In Alfresco, the term “declare” is used in two senses:

  • Declare as Record: The operation of making a regular document into a record (GS API /files/{id}/declare)
  • Declare Record / Complete Record: The operation of finalizing record metadata entry and making it immutable (Legacy API declareRecord action)

11. Executing the Cutoff (Active to Inactive)

In the business scenario, we assume the contract has ended. Complete the “Case Closed” event and execute the cutoff.

11-1. Completing the Event

After confirming the next action status, eventsEligible becomes true.

11-2. Executing the Cutoff

After cutoff, the following changes occur to the record:

  • The rma:cutOff aspect is applied
  • rma:cutOffDate is set
  • The record’s content and metadata become immutable

11-3. Checking the Next Action

The next action is Retain, with asOf set to February 15, 2029 (3 years later). After this date, the retention period expires and the next step (disposal) becomes executable.

When disposal of specific records needs to be temporarily suspended due to litigation or other reasons, the “Hold” feature is used.

12-1. Creating a Hold

12-2. Adding Records to a Hold

Records added to a hold cannot be disposed of even after the retention period has expired.

12-3. Checking Hold Contents

The Share UI Holds screen allows you to view the created holds and the records within them.

12-4. Releasing from Hold

When litigation is resolved, release the record from the hold.

HTTP 204 (No Content) indicates success.

13. User and Group Management

In actual operations, users and groups involved in records management need to be properly configured.

13-1. Creating Users

13-2. Creating Groups and Adding Members

14. Checking Audit Logs

Records Management operations are automatically recorded in audit logs. They can also be checked from the RM management console.

15. Retrieving Record Content (Download)

Lifecycle Overview

The records management lifecycle we experienced in this article can be illustrated as follows.

State Changes at Each Stage

StageOperationApplied AspectMain Properties
Record declarationdeclarerma:recordrma:identifier
Filingfile-rma:dateFiled
Record completiondeclareRecordrma:declaredRecordrma:declaredAt, rma:declaredBy
Cutoffcutoffrma:cutOffrma:cutOffDate
Holdholdrma:frozenrma:frozenAt

API Reference

The following is a list of APIs used in this article.

GS REST API (v1)

OperationMethodEndpoint
Create RM sitePOST/api/-default-/public/gs/versions/1/gs-sites
Get file planGET/api/-default-/public/gs/versions/1/file-plans/-filePlan-
Create categoryPOST/api/-default-/public/gs/versions/1/file-plans/-filePlan-/categories
Create record folderPOST/api/-default-/public/gs/versions/1/record-categories/{id}/children
Declare recordPOST/api/-default-/public/gs/versions/1/files/{id}/declare
File recordPOST/api/-default-/public/gs/versions/1/records/{id}/file
Create retention schedulePOST/api/-default-/public/gs/versions/1/record-categories/{id}/retention-schedules
Create holdPOST/api/-default-/public/gs/versions/1/file-plans/-filePlan-/holds
Add record to holdPOST/api/-default-/public/gs/versions/1/holds/{id}/children
Release record from holdDELETE/api/-default-/public/gs/versions/1/holds/{holdId}/children/{recordId}
Check hold contentsGET/api/-default-/public/gs/versions/1/holds/{id}/children

Legacy API

OperationMethodEndpoint
Add retention actionPOST/alfresco/s/api/node/workspace/SpacesStore/{id}/dispositionschedule/dispositionactiondefinitions
Update retention actionPUT/alfresco/s/api/node/workspace/SpacesStore/{id}/dispositionschedule/dispositionactiondefinitions/{actionId}
Check retention scheduleGET/alfresco/s/api/node/workspace/SpacesStore/{id}/dispositionschedule
Check next actionGET/alfresco/s/api/node/workspace/SpacesStore/{id}/nextdispositionaction
Execute actionPOST/alfresco/s/api/rma/actions/ExecutionQueue
Check audit logGET/alfresco/s/api/rma/admin/rmauditlog
List available valuesGET/alfresco/s/api/rma/admin/listofvalues

Alfresco REST API (v1)

OperationMethodEndpoint
Get user infoGET/api/-default-/public/alfresco/versions/1/people/-me-
Upload filePOST/api/-default-/public/alfresco/versions/1/nodes/{id}/children
Get node infoGET/api/-default-/public/alfresco/versions/1/nodes/{id}
Get contentGET/api/-default-/public/alfresco/versions/1/nodes/{id}/content
Version historyGET/api/-default-/public/alfresco/versions/1/nodes/{id}/versions
Create userPOST/api/-default-/public/alfresco/versions/1/people
Create groupPOST/api/-default-/public/alfresco/versions/1/groups
Add group memberPOST/api/-default-/public/alfresco/versions/1/groups/{id}/members
List sitesGET/api/-default-/public/alfresco/versions/1/sites

Login Failure After Extended Operation

On the Azure virtual machine (8 GiB RAM) used in the previous article, an issue was observed where login became impossible after extended operation. The results of investigating the cause are summarized below.

Cause: Swapping Due to Insufficient Memory

The total mem_limit of all containers is as follows:

Servicemem_limit
alfresco1,900 MB
transform-core-aio1,536 MB
share1,024 MB
solr62,048 MB
activemq1,024 MB
postgres512 MB
content-app128 MB
control-center128 MB
proxy (traefik)128 MB
TotalApprox. 8.4 GB

On an 8 GiB VM, the OS itself uses 1-2 GB, so the memory available for containers is effectively only 6-7 GB. This triggers a chain of issues.

Countermeasures

At minimum 12 GB, recommended 16 GB of memory is needed.

2. Extend Authentication Ticket Expiry

By default, authentication tickets expire after 1 hour. The following settings extend it (already included in this article’s compose file).

3. Enable DB Connection Pool Validation

In long-running environments, connection leaks may prevent authentication requests from being processed (already included in this article’s compose file).

4. Diagnostic Steps

If issues occur, follow the diagnostic steps to check container health, memory usage, and database connection status.

Summary

In this article, we experienced the entire records management lifecycle using REST API with Alfresco Governance Services Community Edition 25.3.0.

  • Built a classification system of File Plan -> Category -> Record Folder
  • Document record declaration -> filing -> record completion
  • Retention schedule configuration (cutoff -> retain -> destroy)
  • Cutoff execution for transitioning from active to inactive
  • Hold for legal preservation (temporary suspension of disposal)
  • User/group management and audit log verification

There were many things that could only be learned by actually trying them, such as the need to distinguish between the GS REST API (v1) and the Legacy API. In particular, the finding that adding retention actions works more reliably with the Legacy API is useful knowledge for actual operations.

Additionally, the cause and countermeasures for the login failure due to insufficient memory were also summarized. When operating Alfresco in a Docker environment, ensuring sufficient memory is important.