diff --git a/gso/migrations/versions/2023-04-21_d3616a686094_add_iptrunk_create_workflow.py b/gso/migrations/versions/2023-04-21_d3616a686094_add_iptrunk_create_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..0380996f37ec80ff32dc8ff855640ca3fb7bcec1
--- /dev/null
+++ b/gso/migrations/versions/2023-04-21_d3616a686094_add_iptrunk_create_workflow.py
@@ -0,0 +1,39 @@
+"""add Iptrunk create  workflow.
+
+Revision ID: d3616a686094
+Revises: 42d52099c7da
+Create Date: 2023-04-21 12:22:18.243625
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = 'd3616a686094'
+down_revision = '42d52099c7da'
+branch_labels = None
+depends_on = None
+
+
+from orchestrator.migrations.helpers import create_workflow, delete_workflow
+
+new_workflows = [
+    {
+        "name": "create_iptrunk",
+        "target": "CREATE",
+        "description": "Create Iptrunk",
+        "product_type": "Iptrunk"
+    }
+]
+
+
+def upgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        create_workflow(conn, workflow)
+
+
+def downgrade() -> None:
+    conn = op.get_bind()
+    for workflow in new_workflows:
+        delete_workflow(conn, workflow["name"])
diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py
index d93b338a9c01be9a2e5c3fee0c46f341ef5c98ae..b768502bfcfc93ae3473581f3349368c99503b53 100644
--- a/gso/workflows/__init__.py
+++ b/gso/workflows/__init__.py
@@ -3,3 +3,4 @@ from orchestrator.workflows import LazyWorkflowInstance
 LazyWorkflowInstance("workflows.device.create_device", "create_device")
 LazyWorkflowInstance("workflows.device.terminate_device", "terminate_device")
 LazyWorkflowInstance("workflows.device.get_facts", "get_facts")
+LazyWorkflowInstance("workflows.iptrunk.create_iptrunk", "create_iptrunk")
diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py
new file mode 100644
index 0000000000000000000000000000000000000000..72be7cb7481ab5be36452edc29e916d4719a2d79
--- /dev/null
+++ b/gso/workflows/iptrunk/create_iptrunk.py
@@ -0,0 +1,196 @@
+import ipaddress
+from uuid import uuid4
+
+from orchestrator.forms import FormPage
+from orchestrator.targets import Target
+from orchestrator.workflow import inputstep
+from orchestrator.forms.validators import Accept
+from orchestrator.types import FormGenerator, State
+from orchestrator.types import SubscriptionLifecycle, UUIDstr
+from orchestrator.workflow import done, init, step, workflow
+from orchestrator.workflows.steps import resync, set_status
+from orchestrator.workflows.steps import store_process_subscription
+from orchestrator.workflows.utils import wrap_create_initial_input_form
+from gso.products.product_types import iptrunk
+from gso.services import ipam, provisioning_proxy
+
+
+def initial_input_form_generator(product_name: str) -> FormGenerator:
+    class CreateIptrunkForm(FormPage):
+        class Config:
+            title = product_name
+
+        fqdn: str
+
+    user_input = yield CreateIptrunkForm
+
+    return user_input.dict()
+
+
+@step("Create subscription")
+def create_subscription(product: UUIDstr) -> State:
+    subscription = iptrunk.IptrunkInactive.from_product_id(product, uuid4())
+
+    return {
+        "subscription": subscription,
+        "subscription_id": subscription.subscription_id,
+    }
+
+
+@step("Get information from IPAM ")
+def get_info_from_ipam(subscription: iptrunk.IptrunkInactive) -> State:
+    lo = ipam.new_iptrunk_lo_address()
+    subscription.iptrunk.lo_ipv4_address = lo.v4
+    subscription.iptrunk.lo_ipv6_address = lo.v6
+    # TODO: get info about how these should be generated
+    subscription.iptrunk.lo_iso_address = "49.51e5.0001.0620.4009.6047.00"
+    subscription.iptrunk.si_ipv4_network = "192.168.0.0/31"
+    subscription.iptrunk.ias_lt_ipv4_network = "192.168.1.0/31"
+    subscription.iptrunk.ias_lt_ipv6_network = "fc00:798:1::150/126"
+    return {"subscription": subscription}
+
+
+@step("get information about SNMP")
+def get_snmp_info(subscription: iptrunk.IptrunkInactive) -> State:
+    country = 'Spain'
+    city = 'Barcelona'
+    country_code = 'ES'
+    latitude = '41.3743'
+    longitude = '2.1328'
+    subscription.iptrunk.site_country = country
+    subscription.iptrunk.site_city = city
+    subscription.iptrunk.site_country_code = country_code
+    subscription.iptrunk.site_latitude = latitude
+    subscription.iptrunk.site_longitude = longitude
+    subscription.iptrunk.snmp_location = (
+        f'{city.upper()},{country.upper()}[{latitude},{longitude}]'
+    )
+
+    return {"subscription": subscription}
+
+
+@step("Initialize subscription")
+def initialize_subscription(
+    subscription: iptrunk.IptrunkInactive,
+    fqdn: str,
+    ts_address: ipaddress.IPv4Address,
+    ts_port: str
+) -> State:
+    subscription.iptrunk.fqdn = fqdn
+    subscription.iptrunk.ts_address = str(ts_address)
+    subscription.iptrunk.ts_port = str(ts_port)
+    subscription.iptrunk_vendor = iptrunk_vendor
+    subscription.description = f"Iptrunk {fqdn} type \
+                                ({subscription.iptrunk_type})"
+    subscription = iptrunk.IptrunkProvisioning.from_other_lifecycle(
+        subscription, SubscriptionLifecycle.PROVISIONING
+    )
+
+    return {"subscription": subscription}
+
+
+@step("Provision iptrunk [DRY RUN]")
+def provision_iptrunk_dry(
+    subscription: iptrunk.IptrunkProvisioning,
+    fqdn: str,
+    ts_address: str,
+    ts_port: str
+) -> State:
+    # import ansible_runner
+    #
+    # r = ansible_runner.run(
+    #     private_data_dir="/opt/geant-gap-ansible",
+    #     playbook="base_config.yaml",
+    #     inventory=subscription.iptrunk.fqdn,
+    #     extravars={
+    #         "lo_ipv4_address": str(subscription.iptrunk.lo_ipv4_address),
+    #         "lo_ipv6_address": str(subscription.iptrunk.lo_ipv6_address),
+    #         "lo_iso_address": subscription.iptrunk.lo_iso_address,
+    #         "snmp_location": subscription.iptrunk.snmp_location,
+    #         "si_ipv4_network": str(subscription.iptrunk.si_ipv4_network),
+    #         "lt_ipv4_network": str(subscription.iptrunk.ias_lt_ipv4_network),
+    #         "lt_ipv6_network": str(subscription.iptrunk.ias_lt_ipv6_network),
+    #         "site_country_code": subscription.iptrunk.site_country_code,
+    #         "verb": "deploy",
+    #     },
+    # )
+    # out = r.stdout.read()
+    # out_splitted = out.splitlines()
+    # # if r.rc != 0:
+    # #    raise ValueError("Ansible has failed")
+    # return {"dry_run_output": out_splitted, "return_code": r.rc}
+    provisioning_proxy.provision_node(
+        node_subscription_params=subscription,
+        dry_run=True)
+    # TODO: figure out what to return when we are suspending & waiting
+    #       for the provisioning-proxy to call back
+    return {"return_code": 0}
+
+
+@inputstep("Confirm step", assignee="CHANGES")
+def confirm_step() -> FormGenerator:
+    class ConfirmForm(FormPage):
+        confirm: Accept
+
+    user_input = yield ConfirmForm
+
+    return {"confirm": user_input.confirm}
+
+
+@step("Provision iptrunk [FOR REAL]")
+def provision_iptrunk_real(
+    subscription: iptrunk.IptrunkProvisioning,
+    fqdn: str,
+    ts_address: str,
+    ts_port: str
+) -> State:
+    # import ansible_runner
+    #
+    # r = ansible_runner.run(
+    #     private_data_dir="/opt/geant-gap-ansible",
+    #     playbook="base_config.yaml",
+    #     inventory=subscription.iptrunk.fqdn,
+    #     extravars={
+    #         "lo_ipv4_address": str(subscription.iptrunk.lo_ipv4_address),
+    #         "lo_ipv6_address": str(subscription.iptrunk.lo_ipv6_address),
+    #         "lo_iso_address": subscription.iptrunk.lo_iso_address,
+    #         "snmp_location": subscription.iptrunk.snmp_location,
+    #         "si_ipv4_network": str(subscription.iptrunk.si_ipv4_network),
+    #         "lt_ipv4_network": str(subscription.iptrunk.ias_lt_ipv4_network),
+    #         "lt_ipv6_network": str(subscription.iptrunk.ias_lt_ipv6_network),
+    #         "site_country_code": subscription.iptrunk.site_country_code,
+    #         "verb": "deploy",
+    #     },
+    # )
+    # out = r.stdout.read()
+    # out_splitted = out.splitlines()
+    #
+    # return {"real_run_output": out_splitted, "return_code": r.rc}
+    provisioning_proxy.provision_node(
+        node_subscription_params=subscription)
+    # TODO: figure out what to return when we are suspending & waiting
+    #       for the provisioning-proxy to call back
+    return {"return_code": 0}
+
+
+@workflow(
+    "Create Iptrunk",
+    initial_input_form=wrap_create_initial_input_form(
+                       initial_input_form_generator),
+    target=Target.CREATE,
+)
+def create_iptrunk():
+    return (
+        init
+        >> create_subscription
+        >> store_process_subscription(Target.CREATE)
+        >> get_info_from_ipam
+        >> get_snmp_info
+        >> initialize_subscription
+        >> provision_iptrunk_dry
+        >> confirm_step
+        >> provision_iptrunk_real
+        >> set_status(SubscriptionLifecycle.ACTIVE)
+        >> resync
+        >> done
+    )