diff --git a/Dockerfile b/Dockerfile
index 646bd9f13042d70535a85b4f669e3cdf36e6b674..40a48076b8af542688b2181b4c4b795c5bf6dd16 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,12 +1,18 @@
 FROM python:3.12.7-alpine
 WORKDIR /app
 
+# Set environment variables for predictable Python behavior and UTF-8 encoding
+ENV PYTHONUNBUFFERED=1 \
+    PYTHONDONTWRITEBYTECODE=1 \
+    LANG=C.UTF-8 \
+    LC_ALL=C.UTF-8
+
 ARG ARTIFACT_VERSION
 
 RUN apk add --no-cache gcc libc-dev libffi-dev curl vim && \
     addgroup -S appgroup && adduser -S appuser -G appgroup -h /app
 
-RUN pip install \
+RUN pip install --no-cache-dir \
     --pre \
     --trusted-host 150.254.211.2 \
     --extra-index-url https://150.254.211.2/artifactory/api/pypi/geant-swd-pypi/simple \
@@ -19,7 +25,7 @@ ENV TRANSLATIONS_DIR=/app/gso/translations/
 # Copy the shell scripts and ensure scripts do not have Windows line endings and make them executable
 COPY start-app.sh start-worker.sh start-scheduler.sh /app/
 RUN sed -i 's/\r$//' start-app.sh start-worker.sh start-scheduler.sh && \
-    chmod 755 start-app.sh start-worker.sh start-scheduler.sh
+    chmod +x start-app.sh start-worker.sh start-scheduler.sh
 
 RUN chown -R appuser:appgroup /app
 USER appuser
diff --git a/gso/__init__.py b/gso/__init__.py
index ccbb2d0df7993b2fb35a88781f053de344ed2143..56c9c7d4615be7e6964c95025dab3fc078af40e9 100644
--- a/gso/__init__.py
+++ b/gso/__init__.py
@@ -17,6 +17,7 @@ import gso.workflows  # noqa: F401
 from gso.api import router as api_router
 from gso.auth.oidc import oidc_instance
 from gso.auth.opa import graphql_opa_instance, opa_instance
+from gso.graphql_api.resolvers.customer import custom_subscription_interface
 from gso.graphql_api.types import GSO_SCALAR_OVERRIDES
 from gso.settings import load_oss_params
 
@@ -35,7 +36,7 @@ def init_gso_app() -> OrchestratorCore:
     app.register_authentication(oidc_instance)
     app.register_authorization(opa_instance)
     app.register_graphql_authorization(graphql_opa_instance)
-    app.register_graphql()
+    app.register_graphql(subscription_interface=custom_subscription_interface)
     app.include_router(api_router, prefix="/api")
 
     if app_settings.EXECUTOR == ExecutorType.WORKER:
diff --git a/gso/graphql_api/resolvers/__init__.py b/gso/graphql_api/resolvers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..decfd8226c967a94bf2560b00cb3f62be8a00f10
--- /dev/null
+++ b/gso/graphql_api/resolvers/__init__.py
@@ -0,0 +1 @@
+"""resolvers module."""
diff --git a/gso/graphql_api/resolvers/customer.py b/gso/graphql_api/resolvers/customer.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8187a69ca00f4941ebaa104eab2a98880b74055
--- /dev/null
+++ b/gso/graphql_api/resolvers/customer.py
@@ -0,0 +1,30 @@
+"""This module contains the resolver for the customer field in the subscription and process types."""
+
+import strawberry
+from orchestrator.graphql.schemas.customer import CustomerType
+from orchestrator.graphql.schemas.process import ProcessType
+from orchestrator.graphql.schemas.subscription import SubscriptionInterface
+from orchestrator.graphql.utils.override_class import override_class
+
+from gso.services.partners import get_partner_by_id
+
+
+async def resolve_customer(root: CustomerType) -> CustomerType:
+    """Resolve the customer field for a subscription or process."""
+    partner = get_partner_by_id(root.customer_id)
+
+    return CustomerType(
+        customer_id=partner.partner_id,
+        fullname=partner.name,
+        shortcode=partner.email,
+    )
+
+
+customer_field = strawberry.field(
+    resolver=resolve_customer,
+    description="Returns customer of a subscription",
+)
+customer_field.name = "customer"  # type: ignore[attr-defined]
+
+override_class(ProcessType, [customer_field])  # type: ignore[list-item]
+custom_subscription_interface = override_class(SubscriptionInterface, [customer_field])  # type: ignore[list-item]
diff --git a/gso/migrations/versions/2024-12-18_8a65d0ed588e_add_site_connectivity_check_task.py b/gso/migrations/versions/2024-12-18_8a65d0ed588e_add_site_connectivity_check_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c3e61e9e66afc45f8fb92c5663b2e41428c8303
--- /dev/null
+++ b/gso/migrations/versions/2024-12-18_8a65d0ed588e_add_site_connectivity_check_task.py
@@ -0,0 +1,39 @@
+"""Add Site connectivity check task.
+
+Revision ID: 8a65d0ed588e
+Revises: 818d4ffe65df
+Create Date: 2024-12-18 14:36:35.886366
+
+"""
+from uuid import uuid4
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '8a65d0ed588e'
+down_revision = '818d4ffe65df'
+branch_labels = None
+depends_on = None
+
+workflow = {
+    "name": "task_check_site_connectivity",
+    "target": "SYSTEM",
+    "description": "Check Site Connectivity",
+    "workflow_id": uuid4(),
+}
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(
+        sa.text(
+            "INSERT INTO workflows VALUES (:workflow_id, :name, :target, :description, now()) ON CONFLICT DO NOTHING"
+        ),
+        workflow,
+    )
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    conn.execute(sa.text("DELETE FROM workflows WHERE name = :name"), {"name": workflow["name"]})
diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json
index c62d1c4d0601eeee8a1c4e0553f7685f2f7b4fbd..4dfe84023f01061ec7c45b0f84740827482172a2 100644
--- a/gso/translations/en-GB.json
+++ b/gso/translations/en-GB.json
@@ -103,6 +103,7 @@
         "task_modify_partners": "Modify partner task",
         "task_delete_partners": "Delete partner task",
         "task_clean_old_tasks": "Remove old cleanup tasks",
+        "task_check_site_connectivity": "Check NETCONF connectivity of a Site",
         "promote_p_to_pe": "Promote P to PE",
         "create_layer_2_circuit": "Create Layer 2 Circuit",
         "modify_layer_2_circuit": "Modify Layer 2 Circuit",
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index cc6725d47cdf05b2a0959920d7f08455987232d3..20e652d4400a387ef852b9751cb27d5037bdbe9f 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -107,6 +107,7 @@ LazyWorkflowInstance("gso.workflows.tasks.create_partners", "task_create_partner
 LazyWorkflowInstance("gso.workflows.tasks.modify_partners", "task_modify_partners")
 LazyWorkflowInstance("gso.workflows.tasks.delete_partners", "task_delete_partners")
 LazyWorkflowInstance("gso.workflows.tasks.clean_old_tasks", "task_clean_old_tasks")
+LazyWorkflowInstance("gso.workflows.tasks.check_site_connectivity", "task_check_site_connectivity")
 
 #  Edge port workflows
 LazyWorkflowInstance("gso.workflows.edge_port.create_edge_port", "create_edge_port")
diff --git a/gso/workflows/tasks/check_site_connectivity.py b/gso/workflows/tasks/check_site_connectivity.py
new file mode 100644
index 0000000000000000000000000000000000000000..a373d144ce19828133bce7c3be4f9f61e2903a8a
--- /dev/null
+++ b/gso/workflows/tasks/check_site_connectivity.py
@@ -0,0 +1,46 @@
+"""A task for checking site connectivity.
+
+When a new router is planned to be deployed, it is good practice to verify that the OOB connectivity is functioning
+correctly. This task takes a site and a port number as input, and checks whether there is reachability across the OOB
+access.
+"""
+
+from orchestrator import workflow
+from orchestrator.forms import SubmitFormPage
+from orchestrator.targets import Target
+from orchestrator.workflow import StepList, begin, done, step
+from pydantic import ConfigDict
+from pydantic_forms.types import FormGenerator, UUIDstr
+
+from gso.products.product_types.site import Site
+from gso.services.lso_client import LSOState, lso_interaction
+from gso.utils.helpers import active_site_selector
+from gso.utils.types.ip_address import PortNumber
+
+
+def _initial_input_form_generator() -> FormGenerator:
+    class CheckSiteConnectivityForm(SubmitFormPage):
+        model_config = ConfigDict(title="Verify NETCONF connectivity to a Site")
+
+        site: active_site_selector()  # type: ignore[valid-type]
+        port: PortNumber
+
+    user_input = yield CheckSiteConnectivityForm
+    return user_input.model_dump()
+
+
+@step("Check NETCONF connectivity")
+def check_netconf_connectivity(site: UUIDstr, port: PortNumber) -> LSOState:
+    """Run an Ansible playbook that validates NETCONF connectivity to a Site."""
+    site_subscription = Site.from_subscription(site).site
+    return {
+        "playbook_name": "gap_ansible/playbooks/check_netconf_connectivity.yaml",
+        "extra_vars": {"port_number": port},
+        "inventory": {"all": {"hosts": {site_subscription.site_ts_address: None}}},
+    }
+
+
+@workflow("Check Site Connectivity", _initial_input_form_generator, Target.SYSTEM)
+def task_check_site_connectivity() -> StepList:
+    """Check successful NETCONF connectivity of a Site."""
+    return begin >> lso_interaction(check_netconf_connectivity) >> done
diff --git a/requirements.txt b/requirements.txt
index 97fd1fedbe08dc83073afba7953b608a5fd2a189..bc8fd2b09673f22fef3df8a8dd7356ecd95e2e3a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,11 +1,11 @@
 orchestrator-core==2.8.0
-requests==2.31.0
+requests==2.32.3
 infoblox-client~=0.6.0
 pycountry==23.12.11
 pynetbox==7.3.3
 celery-redbeat==2.2.0
 celery==5.3.6
-azure-identity==1.16.0
+azure-identity==1.19.0
 msgraph-sdk==1.2.0
 ping3==4.0.8
 unidecode==1.3.8
diff --git a/setup.py b/setup.py
index bce5bfc27bfdab4845b989f98d31dc31f276530f..3c152ecec8f344a4be312becfce06d38b2843e3f 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
 
 setup(
     name="geant-service-orchestrator",
-    version="2.28",
+    version="2.29",
     author="GÉANT Orchestration and Automation Team",
     author_email="goat@geant.org",
     description="GÉANT Service Orchestrator",