How to install apps after using the easy_install.py script

I installed Frappe/ERPNext using the easy_install.py script to create containers.
My command went like this:

python3 easy-install.py -p -s erp.mysite.com -n mysite-erp --email me@example.com

It was a success, and I was able to type “erp.mysite.com” into a browser and looks good and is functional.
On my server, this created several containers as follows:

 ✔ Container mysite-erp-redis-cache-1   Started
 ✔ Container mysite-erp-proxy-1         Started
 ✔ Container mysite-erp-db-1            Healthy
 ✔ Container mysite-erp-redis-queue-1   Started
 ✔ Container mysite-erp-configurator-1  Exited
 ✔ Container mysite-erp-scheduler-1     Started
 ✔ Container mysite-erp-queue-long-1    Started
 ✔ Container mysite-erp-backend-1       Started
 ✔ Container mysite-erp-websocket-1     Started 
 ✔ Container mysite-erp-queue-short-1   Started
 ✔ Container mysite-erp-frontend-1      Started 

My question has to do with installing additional apps. I tried to repeat the steps on a YouTube video to do so:

First, I opened a terminal for what I believe is the backend container:

docker exec -it mysite-erp-backend-1 /bin/bash 

Which brings up a shell inside the container.

frappe@1841e4af43d8:~/frappe-bench$

Then I run the following commands:

frappe@1841e4af43d8:~/frappe-bench$ bench get-app payments --branch version-15

Output looks good. Then,

frappe@1841e4af43d8:~/frappe-bench$ bench --site erp.mysite.com install-app payments
Installing payments...
* Installing Payment Custom Fields in Web Form
An error occurred while installing payments: Web Form: Options must be a valid DocType for field Payment Gateway in row 57
Traceback with variables (most recent call last):
  File "apps/frappe/frappe/commands/site.py", line 446, in install_app
    _install_app(app, verbose=context.verbose, force=force)
      context = {'sites': ['erp.mysite.com'], 'force': False, 'verbose': False, 'profile': False}
      apps = ('payments',)
      force = False
      _install_app = <function install_app at 0x7fa494c6afc0>
      filelock = <function filelock at 0x7fa494c69080>
      exit_code = 0
      site = 'erp.mysite.com'
      app = 'payments'
      err = WrongOptionsDoctypeLinkError('Web Form: Options must be a valid DocType for field Payment Gateway in row 57')
  File "apps/frappe/frappe/installer.py", line 309, in install_app
    frappe.get_attr(after_install)()
      name = 'payments'
      verbose = False
      set_as_patched = True
      force = False
      sync_jobs = <function sync_jobs at 0x7fa493ce77e0>
      sync_for = <function sync_for at 0x7fa493cf85e0>
      sync_customizations = <function sync_customizations at 0x7fa494da9620>
      sync_fixtures = <function sync_fixtures at 0x7fa493cf8900>
      app_hooks = {'after_install': ['payments.utils.make_custom_fields'], 'app_description': ['Payments app for frappe'], 'app_email': ['hello@frappe.io'], 'app_license': ['MIT'], 'app_name': ['payments'], 'app_publisher': ['Frappe Technologies'], 'app_title': ['Payments'], 'app_version': ['0.0.1'], 'before_install': ['payments.utils.before_install'], 'before_tests': ['erpnext.setup.utils.before_tests'], 'before_uninstall': ['payments.utils.delete_custom_fields'], 'override_doctype_class': {'Web Form': ['payments.overrides.payment_webform.PaymentWebForm']}, 'override_whitelisted_methods': {'frappe.website.doctype.web_form.web_form.accept': ['payments.overrides.payment_webform.accept']}, 'scheduler_events': {'all': ['payments.payment_gateways.doctype.razorpay_settings.razorpay_settings.capture_payment']}}
      installed_apps = ['frappe', 'erpnext']
      before_install = 'payments.utils.before_install'
      out = None
      after_install = 'payments.utils.make_custom_fields'
  File "apps/payments/payments/utils/utils.py", line 60, in make_custom_fields
    create_custom_fields(
  File "apps/frappe/frappe/custom/doctype/custom_field/custom_field.py", line 325, in create_custom_fields
    create_custom_field(doctype, df, ignore_validate=ignore_validate)
      custom_fields = {'Web Form': [{'fieldname': 'payments_tab', 'fieldtype': 'Tab Break', 'label': 'Payments', 'insert_after': 'custom_css'}, {'default': '0', 'fieldname': 'accept_payment', 'fieldtype': 'Check', 'label': 'Accept Payment', 'insert_after': 'payments'}, {'depends_on': 'accept_payment', 'fieldname': 'payment_gateway', 'fieldtype': 'Link', 'label': 'Payment Gateway', 'options': 'Payment Gateway', 'insert_after': 'accept_payment'}, {'default': 'Buy Now', 'depends_on': 'accept_payment', 'fieldname': 'payment_button_label', 'fieldtype': 'Data', 'label': 'Button Label', 'insert_after': 'payment_gateway'}, {'depends_on': 'accept_payment', 'fieldname': 'payment_button_help', 'fieldtype': 'Text', 'label': 'Button Help', 'insert_after': 'payment_button_label'}, {'fieldname': 'payments_cb', 'fieldtype': 'Column Break', 'insert_after': 'payment_button_help'}, {'default': '0', 'depends_on': 'accept_payment', 'fieldname': 'amount_based_on_field', 'fieldtype': 'Check', 'label': 'Amount Based On Field', 'in...
      ignore_validate = False
      update = True
      doctypes_to_update = {'Web Form'}
      doctypes = ('Web Form',)
      fields = [{'fieldname': 'payments_tab', 'fieldtype': 'Tab Break', 'label': 'Payments', 'insert_after': 'custom_css'}, {'default': '0', 'fieldname': 'accept_payment', 'fieldtype': 'Check', 'label': 'Accept Payment', 'insert_after': 'payments'}, {'depends_on': 'accept_payment', 'fieldname': 'payment_gateway', 'fieldtype': 'Link', 'label': 'Payment Gateway', 'options': 'Payment Gateway', 'insert_after': 'accept_payment'}, {'default': 'Buy Now', 'depends_on': 'accept_payment', 'fieldname': 'payment_button_label', 'fieldtype': 'Data', 'label': 'Button Label', 'insert_after': 'payment_gateway'}, {'depends_on': 'accept_payment', 'fieldname': 'payment_button_help', 'fieldtype': 'Text', 'label': 'Button Help', 'insert_after': 'payment_button_label'}, {'fieldname': 'payments_cb', 'fieldtype': 'Column Break', 'insert_after': 'payment_button_help'}, {'default': '0', 'depends_on': 'accept_payment', 'fieldname': 'amount_based_on_field', 'fieldtype': 'Check', 'label': 'Amount Based On Field', 'insert_after': ...
      doctype = 'Web Form'
      df = {'depends_on': 'accept_payment', 'fieldname': 'payment_gateway', 'fieldtype': 'Link', 'label': 'Payment Gateway', 'options': 'Payment Gateway', 'insert_after': 'accept_payment', 'owner': 'Administrator'}
      field = None
  File "apps/frappe/frappe/custom/doctype/custom_field/custom_field.py", line 291, in create_custom_field
    custom_field.insert()
      doctype = 'Web Form'
      df = {'depends_on': 'accept_payment', 'fieldname': 'payment_gateway', 'fieldtype': 'Link', 'label': 'Payment Gateway', 'options': 'Payment Gateway', 'insert_after': 'accept_payment', 'owner': 'Administrator'}
      ignore_validate = False
      is_system_generated = True
      custom_field = <CustomField: Web Form-payment_gateway>
  File "apps/frappe/frappe/model/document.py", line 310, in insert
    self.run_post_save_methods()
      self = <CustomField: Web Form-payment_gateway>
      ignore_permissions = None
      ignore_links = None
      ignore_if_duplicate = False
      ignore_mandatory = None
      set_name = None
      set_child_names = True
  File "apps/frappe/frappe/model/document.py", line 1106, in run_post_save_methods
    self.run_method("on_update")
      self = <CustomField: Web Form-payment_gateway>
  File "apps/frappe/frappe/model/document.py", line 940, in run_method
    out = Document.hook(fn)(self, *args, **kwargs)
      self = <CustomField: Web Form-payment_gateway>
      method = 'on_update'
      args = ()
      kwargs = {}
      fn = <function Document.run_method.<locals>.fn at 0x7fa4919474c0>
  File "apps/frappe/frappe/model/document.py", line 1300, in composer
    return composed(self, method, *args, **kwargs)
      self = <CustomField: Web Form-payment_gateway>
      args = ()
      kwargs = {}
      hooks = [<function clear_doctype_notifications at 0x7fa491c53e20>, <function process_workflow_actions at 0x7fa491ca5da0>, <function attach_files_to_document at 0x7fa493c8a700>, <function apply at 0x7fa491cc47c0>, <function update_due_date at 0x7fa491cc4860>, <function apply_permissions_for_non_standard_user_type at 0x7fa491cc5f80>]
      method = 'on_update'
      doc_events = {'*': {'on_update': ['frappe.desk.notifications.clear_doctype_notifications', 'frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions', 'frappe.core.doctype.file.utils.attach_files_to_document', 'frappe.automation.doctype.assignment_rule.assignment_rule.apply', 'frappe.automation.doctype.assignment_rule.assignment_rule.update_due_date', 'frappe.core.doctype.user_type.user_type.apply_permissions_for_non_standard_user_type'], 'after_rename': ['frappe.desk.notifications.clear_doctype_notifications'], 'on_cancel': ['frappe.desk.notifications.clear_doctype_notifications', 'frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions', 'frappe.automation.doctype.assignment_rule.assignment_rule.apply'], 'on_trash': ['frappe.desk.notifications.clear_doctype_notifications', 'frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions'], 'on_update_after_submit': ['frappe.workflow.doctype.workflow_action.workflow_action.process_w...
      handler = 'frappe.core.doctype.user_type.user_type.apply_permissions_for_non_standard_user_type'
      composed = <function Document.hook.<locals>.compose.<locals>.runner at 0x7fa491947ba0>
      compose = <function Document.hook.<locals>.compose at 0x7fa4919472e0>
      f = <function Document.run_method.<locals>.fn at 0x7fa4919474c0>
  File "apps/frappe/frappe/model/document.py", line 1282, in runner
    add_to_return_value(self, fn(self, *args, **kwargs))
      self = <CustomField: Web Form-payment_gateway>
      method = 'on_update'
      args = ()
      kwargs = {}
      add_to_return_value = <function Document.hook.<locals>.add_to_return_value at 0x7fa491947ec0>
      fn = <function Document.run_method.<locals>.fn at 0x7fa4919474c0>
      hooks = (<function clear_doctype_notifications at 0x7fa491c53e20>, <function process_workflow_actions at 0x7fa491ca5da0>, <function attach_files_to_document at 0x7fa493c8a700>, <function apply at 0x7fa491cc47c0>, <function update_due_date at 0x7fa491cc4860>, <function apply_permissions_for_non_standard_user_type at 0x7fa491cc5f80>)
  File "apps/frappe/frappe/model/document.py", line 937, in fn
    return method_object(*args, **kwargs)
      self = <CustomField: Web Form-payment_gateway>
      args = ()
      kwargs = {}
      method_object = <bound method CustomField.on_update of <CustomField: Web Form-payment_gateway>>
      method = 'on_update'
  File "apps/frappe/frappe/custom/doctype/custom_field/custom_field.py", line 205, in on_update
    validate_fields_for_doctype(self.dt)
      self = <CustomField: Web Form-payment_gateway>
      validate_fields_for_doctype = <function validate_fields_for_doctype at 0x7fa491944a40>
  File "apps/frappe/frappe/core/doctype/doctype/doctype.py", line 1179, in validate_fields_for_doctype
    validate_fields(meta)
      doctype = 'Web Form'
      meta = <Meta: Web Form>
  File "apps/frappe/frappe/core/doctype/doctype/doctype.py", line 1617, in validate_fields
    check_link_table_options(meta.get("name"), d)
      meta = <Meta: Web Form>
      check_illegal_characters = <function validate_fields.<locals>.check_illegal_characters at 0x7fa491947420>
      check_invalid_fieldnames = <function validate_fields.<locals>.check_invalid_fieldnames at 0x7fa49198c540>
      check_unique_fieldname = <function validate_fields.<locals>.check_unique_fieldname at 0x7fa49198c400>
      check_fieldname_length = <function validate_fields.<locals>.check_fieldname_length at 0x7fa49198c720>
      check_illegal_mandatory = <function validate_fields.<locals>.check_illegal_mandatory at 0x7fa49198c7c0>
      check_link_table_options = <function validate_fields.<locals>.check_link_table_options at 0x7fa49198d8a0>
      check_hidden_and_mandatory = <function validate_fields.<locals>.check_hidden_and_mandatory at 0x7fa49198c4a0>
      check_width = <function validate_fields.<locals>.check_width at 0x7fa49198c5e0>
      check_in_list_view = <function validate_fields.<locals>.check_in_list_view at 0x7fa49198c680>
      check_in_global_search = <function validate_fields.<locals>.check_in_global_search at 0x7fa49198c860>
      check_dynamic_link_options = <function validate_fields.<locals>.check_dynamic_link_options at 0x7fa49198c900>
      check_illegal_default = <function validate_fields.<locals>.check_illegal_default at 0x7fa49198c9a0>
      check_precision = <function validate_fields.<locals>.check_precision at 0x7fa49198ca40>
      check_unique_and_text = <function validate_fields.<locals>.check_unique_and_text at 0x7fa49198cae0>
      check_fold = <function validate_fields.<locals>.check_fold at 0x7fa49198cb80>
      check_search_fields = <function validate_fields.<locals>.check_search_fields at 0x7fa49198cc20>
      check_title_field = <function validate_fields.<locals>.check_title_field at 0x7fa49198ccc0>
      check_image_field = <function validate_fields.<locals>.check_image_field at 0x7fa49198cd60>
      check_is_published_field = <function validate_fields.<locals>.check_is_published_field at 0x7fa49198ce00>
      check_website_search_field = <function validate_fields.<locals>.check_website_search_field at 0x7fa49198cea0>
      check_timeline_field = <function validate_fields.<locals>.check_timeline_field at 0x7fa49198cf40>
      check_sort_field = <function validate_fields.<locals>.check_sort_field at 0x7fa49198cfe0>
      check_illegal_depends_on_conditions = <function validate_fields.<locals>.check_illegal_depends_on_conditions at 0x7fa49198d080>
      check_table_multiselect_option = <function validate_fields.<locals>.check_table_multiselect_option at 0x7fa49198d260>
      scrub_options_in_select = <function validate_fields.<locals>.scrub_options_in_select at 0x7fa49198d120>
      scrub_fetch_from = <function validate_fields.<locals>.scrub_fetch_from at 0x7fa49198d3a0>
      validate_data_field_type = <function validate_fields.<locals>.validate_data_field_type at 0x7fa49198d1c0>
      check_child_table_option = <function validate_fields.<locals>.check_child_table_option at 0x7fa49198d440>
      check_max_height = <function validate_fields.<locals>.check_max_height at 0x7fa49198d300>
      check_no_of_ratings = <function validate_fields.<locals>.check_no_of_ratings at 0x7fa49198d6c0>
      d = <LinkDocField: payment_gateway parent=Web Form>
      fieldname_list = ['form_tab', 'title', 'route', 'published', 'column_break_vdhm', 'doc_type', 'module', 'is_standard', 'section_break_1', 'introduction_text', 'web_form_fields', 'settings_tab', 'login_required', 'allow_multiple', 'allow_edit', 'allow_delete', 'anonymous', 'column_break_2', 'apply_document_permissions', 'allow_print', 'print_format', 'allow_comments', 'show_attachments', 'allow_incomplete', 'section_break_2', 'max_attachment_size', 'condition_section', 'condition_description', 'condition_json', 'section_break_3', 'list_setting_message', 'show_list', 'list_title', 'list_columns', 'section_break_4', 'show_sidebar', 'website_sidebar', 'customization_tab', 'button_label', 'banner_image', 'column_break_3', 'breadcrumbs', 'section_break_5', 'success_title', 'success_url', 'column_break_4', 'success_message', 'meta_section', 'meta_title', 'meta_description', 'column_break_khxs', 'meta_image', 'section_break_6', 'client_script', 'custom_css', 'payments_tab', 'payment_gateway', 'accept_payment']
      fields = [<Tab BreakDocField: form_tab parent=Web Form>, <DataDocField: title parent=Web Form>, <DataDocField: route parent=Web Form>, <CheckDocField: published parent=Web Form>, <Column BreakDocField: column_break_vdhm parent=Web Form>, <LinkDocField: doc_type parent=Web Form>, <LinkDocField: module parent=Web Form>, <CheckDocField: is_standard parent=Web Form>, <Section BreakDocField: section_break_1 parent=Web Form>, <Text EditorDocField: introduction_text parent=Web Form>, <TableDocField: web_form_fields parent=Web Form>, <Tab BreakDocField: settings_tab parent=Web Form>, <CheckDocField: login_required parent=Web Form>, <CheckDocField: allow_multiple parent=Web Form>, <CheckDocField: allow_edit parent=Web Form>, <CheckDocField: allow_delete parent=Web Form>, <CheckDocField: anonymous parent=Web Form>, <Column BreakDocField: column_break_2 parent=Web Form>, <CheckDocField: apply_document_permissions parent=Web Form>, <CheckDocField: allow_print parent=Web Form>, <LinkDocField: print_format p...
      not_allowed_in_list_view = ['Section Break', 'Column Break', 'Tab Break', 'HTML', 'Table', 'Table MultiSelect', 'Button', 'Image', 'Fold', 'Heading', 'Attach Image']
  File "apps/frappe/frappe/core/doctype/doctype/doctype.py", line 1254, in check_link_table_options
    frappe.throw(
      docname = 'Web Form'
      d = <LinkDocField: payment_gateway parent=Web Form>
      options = None
  File "apps/frappe/frappe/__init__.py", line 589, in throw
    msgprint(
      msg = 'Web Form: Options must be a valid DocType for field Payment Gateway in row 57'
      exc = <class 'frappe.core.doctype.doctype.doctype.WrongOptionsDoctypeLinkError'>
      title = None
      is_minimizable = False
      wide = False
      as_list = False
  File "apps/frappe/frappe/__init__.py", line 561, in msgprint
    _raise_exception()
      msg = 'Web Form: Options must be a valid DocType for field Payment Gateway in row 57'
      title = None
      raise_exception = <class 'frappe.core.doctype.doctype.doctype.WrongOptionsDoctypeLinkError'>
      as_table = False
      as_list = False
      indicator = 'red'
      alert = False
      primary_action = None
      is_minimizable = False
      wide = False
      realtime = False
      sys = <module 'sys' (built-in)>
      _raise_exception = <function msgprint.<locals>._raise_exception at 0x7fa49198d580>
      _strip_html_tags = <functools._lru_cache_wrapper object at 0x7fa491965dd0>
      inspect = <module 'inspect' from '/usr/local/lib/python3.11/inspect.py'>
      out = {'message': 'Web Form: Options must be a valid DocType for field Payment Gateway in row 57', 'title': 'Message', 'indicator': 'red', 'raise_exception': 1, '__frappe_exc_id': 'e15e8409ed65eac5cef4f57f448cb267f749f4e4b3c18552db14452a'}
      strip_html_tags = <function strip_html_tags at 0x7fa495ae4720>
  File "apps/frappe/frappe/__init__.py", line 512, in _raise_exception
    raise exc
      exc = WrongOptionsDoctypeLinkError('Web Form: Options must be a valid DocType for field Payment Gateway in row 57')
      inspect = <module 'inspect' from '/usr/local/lib/python3.11/inspect.py'>
      msg = 'Web Form: Options must be a valid DocType for field Payment Gateway in row 57'
      out = {'message': 'Web Form: Options must be a valid DocType for field Payment Gateway in row 57', 'title': 'Message', 'indicator': 'red', 'raise_exception': 1, '__frappe_exc_id': 'e15e8409ed65eac5cef4f57f448cb267f749f4e4b3c18552db14452a'}
      raise_exception = <class 'frappe.core.doctype.doctype.doctype.WrongOptionsDoctypeLinkError'>
frappe.core.doctype.doctype.doctype.WrongOptionsDoctypeLinkError: Web Form: Options must be a valid DocType for field Payment Gateway in row 57

Obviously, looks pretty bad. And my browser shows: Internal Server Error. What’s the next step here?

Ok, so restarting the container mysite-erp-backend-1 actually brings the site back, but the installation of payments didn’t take. I’m guessing I have to make this happen on the frontend container but I’m unsure how.

docker exec mysite-erp-backend-1 bench migrate

Did the trick. I’ll keep this posted if anything else goes wrong.