diff --git a/geant/gap_ansible/playbooks/edge_port.yaml b/geant/gap_ansible/playbooks/edge_port.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..340d41d18592c694dae6dbb24b9b98de48ca1c18
--- /dev/null
+++ b/geant/gap_ansible/playbooks/edge_port.yaml
@@ -0,0 +1,5 @@
+- name: Create Edge Port
+  hosts: all
+  gather_facts: false
+  roles:
+    - ../roles/edge_port
diff --git a/geant/gap_ansible/roles/edge_port/README.md b/geant/gap_ansible/roles/edge_port/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a090d1c2f83906723ddd9b1473a6ba9d53f643a1
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/README.md
@@ -0,0 +1,36 @@
+Role Name
+=========
+
+A role for configuring Edge (access) ports on Nokia SR routers.
+
+Requirements
+------------
+
+GEANT custom netconf module with Nokia "commit_comment" and "config_mode" features.
+
+Role Variables
+--------------
+
+- vars/main.yaml
+- external inventory (group_vars)
+- orchestrator (GSO)
+
+Dependencies
+------------
+
+n/a
+
+Example Playbook
+----------------
+
+Role is supposed to be driven by GSO.
+
+License
+-------
+
+MIT
+
+Author Information
+------------------
+
+A. Kurbatov, S. Spinelli. GEANT Orchestration and Automation Team (GOAT).
diff --git a/geant/gap_ansible/roles/edge_port/defaults/main.yml b/geant/gap_ansible/roles/edge_port/defaults/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0d82f9c0eec79ce7970fe472772ad1e5f91983b3
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+# defaults file for edge_port
diff --git a/geant/gap_ansible/roles/edge_port/handlers/main.yml b/geant/gap_ansible/roles/edge_port/handlers/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6e01eb9aca73d63936b9a8a72365217b06be1b13
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/handlers/main.yml
@@ -0,0 +1,2 @@
+---
+# handlers file for edge_port
diff --git a/geant/gap_ansible/roles/edge_port/meta/main.yml b/geant/gap_ansible/roles/edge_port/meta/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4653b30351f55924278063c3c8311bcfd73f8469
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/meta/main.yml
@@ -0,0 +1,38 @@
+galaxy_info:
+  author: A. Kurbatov, S. Spinelli
+  description: GEANT Orchestration and Automation Team
+  company: GEANT
+
+  # If the issue tracker for your role is not on github, uncomment the
+  # next line and provide a value
+  # issue_tracker_url: http://example.com/issue/tracker
+
+  # Choose a valid license ID from https://spdx.org - some suggested licenses:
+  # - BSD-3-Clause (default)
+  # - MIT
+  # - GPL-2.0-or-later
+  # - GPL-3.0-only
+  # - Apache-2.0
+  # - CC-BY-4.0
+  license: MIT
+
+  min_ansible_version: '2.10'
+
+  # If this a Container Enabled role, provide the minimum Ansible Container version.
+  # min_ansible_container_version:
+
+  galaxy_tags:
+    - network
+    # List tags for your role here, one per line. A tag is a keyword that describes
+    # and categorizes the role. Users find roles by searching for tags. Be sure to
+    # remove the '[]' above, if you add tags to this list.
+    #
+    # NOTE: A tag is limited to a single word comprised of alphanumeric characters.
+    #       Maximum 20 tags per role.
+
+dependencies: []
+  # List your role dependencies here, one per line. Be sure to remove the '[]' above,
+  # if you add dependencies to this list.
+
+collections:
+  - geant.gap_ansible
diff --git a/geant/gap_ansible/roles/edge_port/tasks/compile.yaml b/geant/gap_ansible/roles/edge_port/tasks/compile.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6c7fc2c36ec3ad7da2d1fb2bc4fcceb37fe21693
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/tasks/compile.yaml
@@ -0,0 +1,24 @@
+---
+- name: Set ansible host to localhost to compile config when router is offline
+  when:
+    router.router_access_via_ts | ansible.builtin.bool
+  ansible.builtin.set_fact:
+    ansible_host: "localhost"
+    ansible_connection: local
+
+- name: Create a folder for all the things
+  ansible.builtin.file:
+    path: "/var/tmp/ansible_run_{{ opid }}"
+    state: directory
+    mode: '0755'
+  delegate_to: localhost
+
+- name: Print the template in "/var/tmp/ansible_run_{{ opid }}/edge_port_{{ verb }}.conf"
+  when: verb == "create"
+  ansible.builtin.template:
+    src: "{{ router.vendor }}/edge_port_{{ verb }}.j2"
+    dest: "/var/tmp/ansible_run_{{ opid }}/edge_port_{{ verb }}.conf"
+    lstrip_blocks: true
+    trim_blocks: true
+    mode: '0755'
+  delegate_to: localhost
diff --git a/geant/gap_ansible/roles/edge_port/tasks/connection_tasks.yaml b/geant/gap_ansible/roles/edge_port/tasks/connection_tasks.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..56768fbf4d4364c78a9c879dd0b7852133418deb
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/tasks/connection_tasks.yaml
@@ -0,0 +1,11 @@
+---
+- name: Set ansible_host to terminal server when router is offline
+  when: router.router_access_via_ts | ansible.builtin.bool
+  ansible.builtin.set_fact:
+    ansible_host: "{{ router.router_site.site_ts_address }}"
+    ansible_port: "{{ router.router_ts_port }}"
+
+- name: Load netconf connection config
+  ansible.builtin.set_fact:
+    ansible_connection: "{{ netconf_access[router.vendor].ansible_connection }}"
+    ansible_network_os: "{{ netconf_access[router.vendor].ansible_network_os }}"
diff --git a/geant/gap_ansible/roles/edge_port/tasks/deploy.yaml b/geant/gap_ansible/roles/edge_port/tasks/deploy.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f6e3ee638440f299fcaefbfd7fa9c747bb6fc1e5
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/tasks/deploy.yaml
@@ -0,0 +1,38 @@
+---
+- name: Perform "{{ verb }}" Edge port on  "{{ inventory_hostname }}" [CHECK ONLY][NOKIA]
+  when: >-
+      dry_run | ansible.builtin.bool
+  geant.gap_ansible.nokia_netconf_config:
+    format: xml
+    default_operation: merge
+    content: "{{ lookup('ansible.builtin.template', '{{ router.vendor }}/edge_port_{{ verb }}.j2') }}"
+    commit: true
+    validate: true
+    config_mode: private
+  diff: true
+  register: output
+  check_mode: true
+
+
+- name: Fail if there is any diff
+  ansible.builtin.fail:
+    msg: Base config drift detected!!!
+  when: >
+    output.changed | ansible.builtin.bool
+    and
+    is_verification_workflow | ansible.builtin.bool
+
+
+- name: Perform "{{ verb }}" Edge port on  "{{ inventory_hostname }}" [FOR REAL][NOKIA]
+  when: >-
+      not (dry_run | ansible.builtin.bool)
+  geant.gap_ansible.nokia_netconf_config:
+    format: xml
+    default_operation: merge
+    content: "{{ lookup('ansible.builtin.template', '{{ router.vendor }}/edge_port_{{ verb }}.j2') }}"
+    commit: true
+    commit_comment: "{{ commit_comment }}"
+    config_mode: private
+  diff: true
+  register: output
+  check_mode: false
diff --git a/geant/gap_ansible/roles/edge_port/tasks/main.yml b/geant/gap_ansible/roles/edge_port/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4a0d1f4c22127a4714c01d43249dab195ade9f76
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/tasks/main.yml
@@ -0,0 +1,15 @@
+---
+# tasks file for edge_port
+- name: Include Standard role tasks
+  ansible.builtin.include_tasks: standard_tasks.yaml
+
+- name: Include templates compilation
+  when: verb in verbs
+  ansible.builtin.include_tasks: compile.yaml
+
+- name: Include set connection tasks
+  ansible.builtin.include_tasks: connection_tasks.yaml
+
+- name: Include deploy tasks
+  when: verb in verbs
+  ansible.builtin.include_tasks: deploy.yaml
diff --git a/geant/gap_ansible/roles/edge_port/tasks/standard_tasks.yaml b/geant/gap_ansible/roles/edge_port/tasks/standard_tasks.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f0bc6dea1395562feca38e574974bdccfe9ace16
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/tasks/standard_tasks.yaml
@@ -0,0 +1,33 @@
+---
+- name: Print the usage
+  when: (verb is not defined) or (verb not in verbs)
+  ansible.builtin.debug:
+    msg:
+      - "'verb' keyword is mandatory. Usage: -e verb=$verb"
+
+- name: Print defined verbs
+  when: (verb is not defined) or (verb not in verbs)
+  ansible.builtin.debug:
+    msg:
+      - "Allowed verb: {{ item }}"
+  loop: "{{ verbs }}"
+
+- name: Stop if arguments are incorrect
+  when: (verb is not defined) or (verb not in verbs)
+  ansible.builtin.meta: end_play
+
+- name: Import routers variables
+  ansible.builtin.include_vars:
+    dir: /opt/ansible_inventory/group_vars/routers
+
+- name: Import variables from 'all'
+  ansible.builtin.include_vars:
+    dir: /opt/ansible_inventory/group_vars/all
+
+- name: Generate an ID for this run
+  ansible.builtin.set_fact:
+    opid: "{{ lookup('community.general.random_string', length=18, special=false) }}"
+
+- name: Print the ID
+  ansible.builtin.debug:
+    msg: "{{ opid }}"
diff --git a/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port.j2 b/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port.j2
new file mode 100644
index 0000000000000000000000000000000000000000..1799ca4efe04f6f5f463aef8af5adfe5eb773261
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port.j2
@@ -0,0 +1,66 @@
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:alu="urn:ietf:params:xml:ns:netconf:base:1.0">
+    <configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf">
+
+{% for member in  ep.edge_port_ae_members %}
+      <port xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="replace">
+          <port-id>{{ member.interface_name }}</port-id>
+          <admin-state>enable</admin-state>
+          <description>PHY {{ ep.edge_port_type }} {{ partner_name }} P_{{ ep.edge_port_name }} |  {{ member.interface_description }}</description>
+          <ethernet>
+              <mode>access</mode>
+              <mtu>{{ mtu_phy | default(9192) }}</mtu>
+            {% if edge_port_lldp_enable_map[ep.edge_port_type] is true %}
+              <lldp>
+                  <dest-mac>
+                      <mac-type>nearest-bridge</mac-type>
+                      <receive>true</receive>
+                      <transmit>true</transmit>
+                      <tx-tlvs>
+                          <port-desc>true</port-desc>
+                          <sys-name>true</sys-name>
+                          <sys-cap>true</sys-cap>
+                      </tx-tlvs>
+                  </dest-mac>
+              </lldp>
+            {% endif %} 
+          </ethernet>
+      </port>
+{% endfor %}
+{% if edge_port_removed_ae_members is defined %}
+  {% for member in edge_port_removed_ae_members %}
+      <port xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="replace">
+          <port-id>{{ member.interface_name }}</port-id>
+          <admin-state>disable</admin-state>
+          <description>''</description>
+      </port>
+  {% endfor %}
+{% endif %}
+      <lag xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="replace">
+          <lag-name>{{ ep.edge_port_name | lower }}</lag-name>
+          <admin-state>enable</admin-state>
+          {% if ep.edge_port_geant_ga_id is string %}
+          <description>LAG {{ ep.edge_port_type }} {{ partner_name }} ${{ ep.edge_port_geant_ga_id }} | </description>
+          {% else %}
+          <description>LAG {{ ep.edge_port_type }} {{ partner_name }} | </description>
+          {% endif %}
+          <mode>access</mode>
+         {% if ep.edge_port_enable_lacp is true %} 
+          <lacp>
+              <mode>active</mode>
+              <administrative-key>{{ (ep.edge_port_name|split("-"))[1] }}</administrative-key>
+          </lacp>
+          {% endif %}
+{% for member in ep.edge_port_ae_members %}
+          <port>
+              <port-id>{{ member.interface_name }}</port-id>
+          </port>
+{% endfor %}
+          {% if ( ep.edge_port_minimum_links | int) > 1 %}
+          <port-threshold>
+            <value>{{ ( ep.edge_port_minimum_links | int) - 1 }}</value>
+            <action>down</action>
+          </port-threshold>
+          {% endif %}
+      </lag>
+  </configure>
+</config>
diff --git a/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port_create.j2 b/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port_create.j2
new file mode 100644
index 0000000000000000000000000000000000000000..296e9fbc28e9f943128d98b361e7748d28eebcc2
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port_create.j2
@@ -0,0 +1,65 @@
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:alu="urn:ietf:params:xml:ns:netconf:base:1.0">
+    <configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf">
+
+{% for member in  ep.edge_port_ae_members %}
+      <port xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="replace">
+          <port-id>{{ member.interface_name }}</port-id>
+          <admin-state>enable</admin-state>
+          <description>PHY {{ ep.edge_port_type }} {{ partner_name }} P_{{ ep.edge_port_name }} | {{ member.interface_description }}</description>
+          <ethernet>
+              <mode>access</mode>
+              <mtu>{{ mtu_phy | default(9192) }}</mtu>
+            {% if edge_port_lldp_enable_map[ep.edge_port_type] is true %}
+              <lldp>
+                  <dest-mac>
+                      <mac-type>nearest-bridge</mac-type>
+                      <receive>true</receive>
+                      <transmit>true</transmit>
+                      <tx-tlvs>
+                          <port-desc>true</port-desc>
+                          <sys-name>true</sys-name>
+                          <sys-cap>true</sys-cap>
+                      </tx-tlvs>
+                  </dest-mac>
+              </lldp>
+            {% endif %}
+          </ethernet>
+      </port>
+{% endfor %}
+{% if removed_ae_members is defined and removed_ae_members|length > 0 %}
+  {% for member in removed_ae_members %}
+      <port xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="replace">
+          <port-id>{{ member.interface_name }}</port-id>
+          <admin-state>disable</admin-state>
+      </port>
+  {% endfor %}
+{% endif %}
+      <lag xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="replace">
+          <lag-name>{{ ep.edge_port_name | lower }}</lag-name>
+          <admin-state>enable</admin-state>
+          {% if ep.geant_ga_id is string %}
+          <description>LAG {{ ep.edge_port_type }} {{ partner_name }} ${{ ep.geant_ga_id }} |</description>
+          {% else %}
+          <description>LAG {{ ep.edge_port_type }} {{ partner_name }} |</description>
+          {% endif %}
+          <mode>access</mode>
+         {% if ep.enable_lacp is true %} 
+          <lacp>
+              <mode>active</mode>
+              <administrative-key>{{ (ep.edge_port_name|split("-"))[1] }}</administrative-key>
+          </lacp>
+          {% endif %}
+{% for member in ep.edge_port_ae_members %}
+          <port>
+              <port-id>{{ member.interface_name }}</port-id>
+          </port>
+{% endfor %}
+          {% if ( ep.minimum_links | int) > 1 %}
+          <port-threshold>
+            <value>{{ ( ep.minimum_links | int) - 1 }}</value>
+            <action>down</action>
+          </port-threshold>
+          {% endif %}
+      </lag>
+  </configure>
+</config>
diff --git a/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port_terminate.j2 b/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port_terminate.j2
new file mode 100644
index 0000000000000000000000000000000000000000..36eed596088a1a5b564b607ef0820da0a3f14bf8
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port_terminate.j2
@@ -0,0 +1,14 @@
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:alu="urn:ietf:params:xml:ns:netconf:base:1.0">
+    <configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf">
+
+{% for member in  ep.edge_port_ae_members %}
+      <port xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="replace">
+          <port-id>{{ member.interface_name }}</port-id>
+          <admin-state>disable</admin-state>
+      </port>
+{% endfor %}
+      <lag xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="delete">
+          <lag-name>{{ ep.edge_port_name | lower }}</lag-name>
+      </lag>
+  </configure>
+</config>
diff --git a/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port_update.j2 b/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port_update.j2
new file mode 120000
index 0000000000000000000000000000000000000000..4f9110ef2350e0a36583633440d625068d7fc5f4
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/templates/nokia/edge_port_update.j2
@@ -0,0 +1 @@
+edge_port_create.j2
\ No newline at end of file
diff --git a/geant/gap_ansible/roles/edge_port/vars/main.yml b/geant/gap_ansible/roles/edge_port/vars/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..362f8c584546901dd367118198e44f2d699e3e0a
--- /dev/null
+++ b/geant/gap_ansible/roles/edge_port/vars/main.yml
@@ -0,0 +1,19 @@
+---
+# vars file for edge_port
+dry_run: true
+is_verification_workflow: false
+
+verbs:
+  - create
+  - update
+  - terminate
+
+ep: "{{ subscription.edge_port }}"
+router: "{{ ep.node }}"
+
+edge_port_lldp_enable_map:
+  INFRASTRUCTURE: true
+  CUSTOMER: true
+  PUBLIC: false
+  RE_INTERCONNECT: false
+  PRIVATE: false