diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index e87a705e10..2371f411a9 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -4,3 +4,9 @@
 # plugins are co-owned
 /InvenTree/plugin/      @SchrodingersGat @matmair
 /InvenTree/plugins/     @SchrodingersGat @matmair
+
+# Installer functions
+.pkgr.yml               @matmair
+Procfile                @matmair
+runtime.txt             @matmair
+/contrib/               @matmair
diff --git a/.pkgr.yml b/.pkgr.yml
new file mode 100644
index 0000000000..9a4cc0f950
--- /dev/null
+++ b/.pkgr.yml
@@ -0,0 +1,46 @@
+name: inventree
+description: Open Source Inventory Management System
+homepage: https://inventree.org
+notifications: false
+buildpack: https://github.com/mjmair/heroku-buildpack-python#v216-mjmair
+env:
+  - STACK=heroku-20
+  - DISABLE_COLLECTSTATIC=1
+  - INVENTREE_DB_ENGINE=sqlite3
+  - INVENTREE_DB_NAME=database.sqlite3
+  - INVENTREE_PLUGINS_ENABLED
+  - INVENTREE_MEDIA_ROOT=/opt/inventree/media
+  - INVENTREE_STATIC_ROOT=/opt/inventree/static
+  - INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt
+  - INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml
+after_install: contrib/packager.io/postinstall.sh
+targets:
+  ubuntu-20.04:
+    dependencies:
+      - curl
+      - python3
+      - python3-venv
+      - python3-pip
+      - python3-cffi
+      - python3-brotli
+      - python3-wheel
+      - libpango-1.0-0
+      - libharfbuzz0b
+      - libpangoft2-1.0-0
+      - gettext
+      - nginx
+      - jq
+  debian-11:
+    dependencies:
+      - curl
+      - python3
+      - python3-venv
+      - python3-pip
+      - python3-cffi
+      - python3-brotli
+      - python3-wheel
+      - libpango-1.0-0
+      - libpangoft2-1.0-0
+      - gettext
+      - nginx
+      - jq
diff --git a/InvenTree/InvenTree/config.py b/InvenTree/InvenTree/config.py
index 4c95e84970..51f194ffb0 100644
--- a/InvenTree/InvenTree/config.py
+++ b/InvenTree/InvenTree/config.py
@@ -22,6 +22,16 @@ def get_base_dir() -> Path:
     return Path(__file__).parent.parent.resolve()
 
 
+def ensure_dir(path: Path) -> None:
+    """Ensure that a directory exists.
+
+    If it does not exist, create it.
+    """
+
+    if not path.exists():
+        path.mkdir(parents=True, exist_ok=True)
+
+
 def get_config_file(create=True) -> Path:
     """Returns the path of the InvenTree configuration file.
 
@@ -39,6 +49,7 @@ def get_config_file(create=True) -> Path:
 
     if not cfg_filename.exists() and create:
         print("InvenTree configuration file 'config.yaml' not found - creating default file")
+        ensure_dir(cfg_filename.parent)
 
         cfg_template = base_dir.joinpath("config_template.yaml")
         shutil.copyfile(cfg_template, cfg_filename)
@@ -169,6 +180,7 @@ def get_plugin_file():
     if not plugin_file.exists():
         logger.warning("Plugin configuration file does not exist - creating default file")
         logger.info(f"Creating plugin file at '{plugin_file}'")
+        ensure_dir(plugin_file.parent)
 
         # If opening the file fails (no write permission, for example), then this will throw an error
         plugin_file.write_text("# InvenTree Plugins (uses PIP framework to install)\n\n")
@@ -201,6 +213,7 @@ def get_secret_key():
 
     if not secret_key_file.exists():
         logger.info(f"Generating random key file at '{secret_key_file}'")
+        ensure_dir(secret_key_file.parent)
 
         # Create a random key file
         options = string.digits + string.ascii_letters + string.punctuation
diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml
index 538a832f21..f1405b48de 100644
--- a/InvenTree/config_template.yaml
+++ b/InvenTree/config_template.yaml
@@ -1,4 +1,8 @@
 
+# Secret key for backend
+# Use the environment variable INVENTREE_SECRET_KEY_FILE
+#secret_key_file: '/etc/inventree/secret_key.txt'
+
 # Database backend selection - Configure backend database settings
 # Documentation: https://inventree.readthedocs.io/en/latest/start/config/
 
@@ -22,6 +26,13 @@ database:
   # HOST: Database host address (if required)
   # PORT: Database host port (if required)
 
+  # --- Database settings ---
+  #ENGINE: sampleengine
+  #NAME: '/path/to/database'
+  #USER: sampleuser
+  #PASSWORD: samplepassword
+  #HOST: samplehost
+  #PORT: sampleport
 
   # --- Example Configuration - MySQL ---
   #ENGINE: mysql
@@ -105,8 +116,8 @@ sentry_enabled: False
 # Set this variable to True to enable InvenTree Plugins
 # Alternatively, use the environment variable INVENTREE_PLUGINS_ENABLED
 plugins_enabled: False
-#plugin_file: /path/to/plugins.txt
-#plugin_dir: /path/to/plugins/
+#plugin_file: '/path/to/plugins.txt'
+#plugin_dir: '/path/to/plugins/'
 
 # Allowed hosts (see ALLOWED_HOSTS in Django settings documentation)
 # A list of strings representing the host/domain names that this Django site can serve.
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000000..d1a313b067
--- /dev/null
+++ b/Procfile
@@ -0,0 +1,2 @@
+web: env/bin/gunicorn --chdir $APP_HOME/InvenTree -c InvenTree/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$PORT
+worker: env/bin/python InvenTree/manage.py qcluster
diff --git a/README.md b/README.md
index a0ca4e75e2..a37c887b52 100644
--- a/README.md
+++ b/README.md
@@ -136,6 +136,11 @@ There are several options to deploy InvenTree.
     <a href="https://inventree.readthedocs.io/en/latest/start/install/">Bare Metal</a>
 </h4></div>
 
+Single line install:
+```bash
+curl https://raw.githubusercontent.com/InvenTree/InvenTree/master/contrib/install.sh | sh
+```
+
 <!-- Contributing -->
 ## :wave: Contributing
 
diff --git a/contrib/install.sh b/contrib/install.sh
new file mode 100755
index 0000000000..ef222dee74
--- /dev/null
+++ b/contrib/install.sh
@@ -0,0 +1,54 @@
+get_distribution() {
+    lsb_dist=""
+    # Every system that we officially support has /etc/os-release
+    if [ -r /etc/os-release ]; then
+        lsb_dist="$(. /etc/os-release && echo "$ID")"
+    fi
+    # Returning an empty string here should be alright since the
+    # case statements don't act unless you provide an actual value
+    echo "$lsb_dist"
+}
+
+get_distribution
+case "$lsb_dist" in
+ubuntu)
+    if command_exists lsb_release; then
+        dist_version="$(lsb_release -r | cut -f2)"
+    fi
+    if [ -z "$dist_version" ] && [ -r /etc/lsb-release ]; then
+        dist_version="$(. /etc/lsb-release && echo "$DISTRIB_RELEASE")"
+    fi
+    ;;
+debian | raspbian)
+    dist_version="$(sed 's/\/.*//' /etc/debian_version | sed 's/\..*//')"
+    lsb_dist="debian"
+    ;;
+centos | rhel | sles)
+    if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
+        dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
+    fi
+    ;;
+*)
+    if command_exists lsb_release; then
+        dist_version="$(lsb_release --release | cut -f2)"
+    fi
+    if [ -z "$dist_version" ] && [ -r /etc/os-release ]; then
+        dist_version="$(. /etc/os-release && echo "$VERSION_ID")"
+    fi
+    ;;
+esac
+echo "### ${lsb_dist} ${dist_version} detected"
+
+# Make sure the depencies are there
+sudo apt-get install wget apt-transport-https -y
+
+echo "### Add key and package source"
+# Add key
+wget -qO- https://dl.packager.io/srv/matmair/InvenTree/key | sudo apt-key add -
+# Add packagelist
+sudo wget -O /etc/apt/sources.list.d/inventree.list https://dl.packager.io/srv/matmair/InvenTree/deploy-test/installer/${lsb_dist}/${dist_version}.repo
+
+echo "### Install InvenTree"
+# Update repos and install inventree
+sudo apt-get update
+sudo apt-get install inventree -y
diff --git a/contrib/packager.io/functions.sh b/contrib/packager.io/functions.sh
new file mode 100755
index 0000000000..a772f38379
--- /dev/null
+++ b/contrib/packager.io/functions.sh
@@ -0,0 +1,289 @@
+#!/bin/bash
+#
+# packager.io postinstall script functions
+#
+
+function detect_docker() {
+  if [ -n "$(grep docker </proc/1/cgroup)" ]; then
+    DOCKER="yes"
+  else
+    DOCKER="no"
+  fi
+}
+
+function detect_initcmd() {
+  if [ -n "$(which systemctl 2>/dev/null)" ]; then
+    INIT_CMD="systemctl"
+  elif [ -n "$(which initctl 2>/dev/null)" ]; then
+    INIT_CMD="initctl"
+  else
+    function sysvinit() {
+      service $2 $1
+    }
+    INIT_CMD="sysvinit"
+  fi
+
+  if [ "${DOCKER}" == "yes" ]; then
+    INIT_CMD="initctl"
+  fi
+}
+
+function detect_ip() {
+  # Get the IP address of the server
+
+  if [ "${SETUP_NO_CALLS}" == "true" ]; then
+    # Use local IP address
+    echo "# Getting the IP address of the first local IP address"
+    export INVENTREE_IP=$(hostname -I | awk '{print $1}')
+  else
+    # Use web service to get the IP address
+    echo "# Getting the IP address of the server via web service"
+    export INVENTREE_IP=$(curl -s https://checkip.amazonaws.com)
+  fi
+
+  echo "IP address is ${INVENTREE_IP}"
+}
+
+function get_env() {
+  envname=$1
+
+  pid=$$
+  while [ -z "${!envname}" -a $pid != 1 ]; do
+      ppid=`ps -oppid -p$pid|tail -1|awk '{print $1}'`
+      env=`strings /proc/$ppid/environ`
+      export $envname=`echo "$env"|awk -F= '$1 == "'$envname'" { print $2; }'`
+      pid=$ppid
+  done
+
+  if [ -n "${SETUP_DEBUG}" ]; then
+    echo "Done getting env $envname: ${!envname}"
+  fi
+}
+
+function detect_local_env() {
+  # Get all possible envs for the install
+
+  if [ -n "${SETUP_DEBUG}" ]; then
+    echo "# Printing local envs - before #++#"
+    printenv
+  fi
+
+  for i in ${SETUP_ENVS//,/ }
+  do
+      get_env $i
+  done
+
+  if [ -n "${SETUP_DEBUG}" ]; then
+    echo "# Printing local envs - after #++#"
+    printenv
+  fi
+}
+
+function detect_envs() {
+  # Detect all envs that should be passed to setup commands
+
+  echo "# Setting base environment variables"
+
+  export INVENTREE_CONFIG_FILE=${CONF_DIR}/config.yaml
+
+  if test -f "${INVENTREE_CONFIG_FILE}"; then
+    echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
+
+    # Install parser
+    pip install jc -q
+
+    # Load config
+    local conf=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
+
+    # Parse the config file
+    export INVENTREE_MEDIA_ROOT=$conf | jq '.[].media_root'
+    export INVENTREE_STATIC_ROOT=$conf | jq '.[].static_root'
+    export INVENTREE_PLUGINS_ENABLED=$conf | jq '.[].plugins_enabled'
+    export INVENTREE_PLUGIN_FILE=$conf | jq '.[].plugin_file'
+    export INVENTREE_SECRET_KEY_FILE=$conf | jq '.[].secret_key_file'
+
+    export INVENTREE_DB_ENGINE=$conf | jq '.[].database.ENGINE'
+    export INVENTREE_DB_NAME=$conf | jq '.[].database.NAME'
+    export INVENTREE_DB_USER=$conf | jq '.[].database.USER'
+    export INVENTREE_DB_PASSWORD=$conf | jq '.[].database.PASSWORD'
+    export INVENTREE_DB_HOST=$conf | jq '.[].database.HOST'
+    export INVENTREE_DB_PORT=$conf | jq '.[].database.PORT'
+  else
+    echo "# No config file found: ${INVENTREE_CONFIG_FILE}, using envs or defaults"
+
+    if [ -n "${SETUP_DEBUG}" ]; then
+      echo "# Print current envs"
+      printenv | grep INVENTREE_
+      printenv | grep SETUP_
+    fi
+
+    export INVENTREE_MEDIA_ROOT=${INVENTREE_MEDIA_ROOT:-${DATA_DIR}/media}
+    export INVENTREE_STATIC_ROOT=${DATA_DIR}/static
+    export INVENTREE_PLUGINS_ENABLED=true
+    export INVENTREE_PLUGIN_FILE=${CONF_DIR}/plugins.txt
+    export INVENTREE_SECRET_KEY_FILE=${CONF_DIR}/secret_key.txt
+
+    export INVENTREE_DB_ENGINE=${INVENTREE_DB_ENGINE:-sqlite3}
+    export INVENTREE_DB_NAME=${INVENTREE_DB_NAME:-${DATA_DIR}/database.sqlite3}
+    export INVENTREE_DB_USER=${INVENTREE_DB_USER:-sampleuser}
+    export INVENTREE_DB_PASSWORD=${INVENTREE_DB_PASSWORD:-samplepassword}
+    export INVENTREE_DB_HOST=${INVENTREE_DB_HOST:-samplehost}
+    export INVENTREE_DB_PORT=${INVENTREE_DB_PORT:-sampleport}
+
+    export SETUP_CONF_LOADED=true
+  fi
+
+  # For debugging pass out the envs
+  echo "# Collected environment variables:"
+  echo "#    INVENTREE_MEDIA_ROOT=${INVENTREE_MEDIA_ROOT}"
+  echo "#    INVENTREE_STATIC_ROOT=${INVENTREE_STATIC_ROOT}"
+  echo "#    INVENTREE_PLUGINS_ENABLED=${INVENTREE_PLUGINS_ENABLED}"
+  echo "#    INVENTREE_PLUGIN_FILE=${INVENTREE_PLUGIN_FILE}"
+  echo "#    INVENTREE_SECRET_KEY_FILE=${INVENTREE_SECRET_KEY_FILE}"
+  echo "#    INVENTREE_DB_ENGINE=${INVENTREE_DB_ENGINE}"
+  echo "#    INVENTREE_DB_NAME=${INVENTREE_DB_NAME}"
+  echo "#    INVENTREE_DB_USER=${INVENTREE_DB_USER}"
+  if [ -n "${SETUP_DEBUG}" ]; then
+    echo "#    INVENTREE_DB_PASSWORD=${INVENTREE_DB_PASSWORD}"
+  fi
+  echo "#    INVENTREE_DB_HOST=${INVENTREE_DB_HOST}"
+  echo "#    INVENTREE_DB_PORT=${INVENTREE_DB_PORT}"
+}
+
+function create_initscripts() {
+
+  # Make sure python env exsists
+  if test -f "${APP_HOME}/env"; then
+    echo "# python enviroment already present - skipping"
+  else
+    echo "# Setting up python enviroment"
+    sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && python3 -m venv env && pip install invoke"
+
+    if [ -n "${SETUP_EXTRA_PIP}" ]; then
+      echo "# Installing extra pip packages"
+      if [ -n "${SETUP_DEBUG}" ]; then
+        echo "# Extra pip packages: ${SETUP_EXTRA_PIP}"
+      fi
+      sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && env/bin/pip install ${SETUP_EXTRA_PIP}"
+    fi
+  fi
+
+  # Unlink default config if it exists
+  if test -f "/etc/nginx/sites-enabled/default"; then
+    echo "# Unlinking default nginx config\n# Old file still in /etc/nginx/sites-available/default"
+    sudo unlink /etc/nginx/sites-enabled/default
+  fi
+
+  # Create InvenTree specific nginx config
+  echo "# Stopping nginx"
+  ${INIT_CMD} stop nginx
+  echo "# Setting up nginx to ${SETUP_NGINX_FILE}"
+  # Always use the latest nginx config; important if new headers are added / needed for security
+  cp ${APP_HOME}/docker/production/nginx.prod.conf ${SETUP_NGINX_FILE}
+  sed -i s/inventree-server:8000/localhost:6000/g ${SETUP_NGINX_FILE}
+  sed -i s=var/www=opt/inventree/data=g ${SETUP_NGINX_FILE}
+  # Start nginx
+  echo "# Starting nginx"
+  ${INIT_CMD} start nginx
+
+  echo "# (Re)creating init scripts"
+  # This reset scale parameters to a known state
+  inventree scale web="1" worker="1"
+
+  echo "# Enabling InvenTree on boot"
+  ${INIT_CMD} enable inventree
+}
+
+function create_admin() {
+  # Create data for admin user
+
+  if test -f "${SETUP_ADMIN_PASSWORD_FILE}"; then
+    echo "# Admin data already exists - skipping"
+  else
+    echo "# Creating admin user data"
+
+    # Static admin data
+    export INVENTREE_ADMIN_USER=${INVENTREE_ADMIN_USER:-admin}
+    export INVENTREE_ADMIN_EMAIL=${INVENTREE_ADMIN_EMAIL:-admin@example.com}
+
+    # Create password if not set
+    if [ -z "${INVENTREE_ADMIN_PASSWORD}" ]; then
+      openssl rand -base64 32 >${SETUP_ADMIN_PASSWORD_FILE}
+      export INVENTREE_ADMIN_PASSWORD=$(cat ${SETUP_ADMIN_PASSWORD_FILE})
+    fi
+  fi
+}
+
+function start_inventree() {
+  echo "# Starting InvenTree"
+  ${INIT_CMD} start inventree
+}
+
+function stop_inventree() {
+  echo "# Stopping InvenTree"
+  ${INIT_CMD} stop inventree
+}
+
+function update_or_install() {
+
+  # Set permissions so app user can write there
+  chown ${APP_USER}:${APP_GROUP} ${APP_HOME} -R
+
+  # Run update as app user
+  echo "# Updating InvenTree"
+  sudo -u ${APP_USER} --preserve-env=$SETUP_ENVS bash -c "cd ${APP_HOME} && invoke update | sed -e 's/^/# inv update| /;'"
+
+  # Make sure permissions are correct again
+  echo "# Set permissions for data dir and media: ${DATA_DIR}"
+  chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} -R
+  chown ${APP_USER}:${APP_GROUP} ${CONF_DIR} -R
+}
+
+function set_env() {
+  echo "# Setting up InvenTree config values"
+
+  inventree config:set INVENTREE_CONFIG_FILE=${INVENTREE_CONFIG_FILE}
+
+  # Changing the config file
+  echo "# Writing the settings to the config file ${INVENTREE_CONFIG_FILE}"
+  # Media Root
+  sed -i s=#media_root:\ \'/home/inventree/data/media\'=media_root:\ \'${INVENTREE_MEDIA_ROOT}\'=g ${INVENTREE_CONFIG_FILE}
+  # Static Root
+  sed -i s=#static_root:\ \'/home/inventree/data/static\'=static_root:\ \'${INVENTREE_STATIC_ROOT}\'=g ${INVENTREE_CONFIG_FILE}
+  # Plugins enabled
+  sed -i s=plugins_enabled:\ False=plugins_enabled:\ ${INVENTREE_PLUGINS_ENABLED}=g ${INVENTREE_CONFIG_FILE}
+  # Plugin file
+  sed -i s=#plugin_file:\ \'/path/to/plugins.txt\'=plugin_file:\ \'${INVENTREE_PLUGIN_FILE}\'=g ${INVENTREE_CONFIG_FILE}
+  # Secret key file
+  sed -i s=#secret_key_file:\ \'/etc/inventree/secret_key.txt\'=secret_key_file:\ \'${INVENTREE_SECRET_KEY_FILE}\'=g ${INVENTREE_CONFIG_FILE}
+  # Debug mode
+  sed -i s=debug:\ True=debug:\ False=g ${INVENTREE_CONFIG_FILE}
+
+  # Database engine
+  sed -i s=#ENGINE:\ sampleengine=ENGINE:\ ${INVENTREE_DB_ENGINE}=g ${INVENTREE_CONFIG_FILE}
+  # Database name
+  sed -i s=#NAME:\ \'/path/to/database\'=NAME:\ \'${INVENTREE_DB_NAME}\'=g ${INVENTREE_CONFIG_FILE}
+  # Database user
+  sed -i s=#USER:\ sampleuser=USER:\ ${INVENTREE_DB_USER}=g ${INVENTREE_CONFIG_FILE}
+  # Database password
+  sed -i s=#PASSWORD:\ samplepassword=PASSWORD:\ ${INVENTREE_DB_PASSWORD}=g ${INVENTREE_CONFIG_FILE}
+  # Database host
+  sed -i s=#HOST:\ samplehost=HOST:\ ${INVENTREE_DB_HOST}=g ${INVENTREE_CONFIG_FILE}
+  # Database port
+  sed -i s=#PORT:\ sampleport=PORT:\ ${INVENTREE_DB_PORT}=g ${INVENTREE_CONFIG_FILE}
+
+  # Fixing the permissions
+  chown ${APP_USER}:${APP_GROUP} ${DATA_DIR} ${INVENTREE_CONFIG_FILE}
+}
+
+function final_message() {
+  echo -e "####################################################################################"
+  echo -e "This InvenTree install uses nginx, the settings for the webserver can be found in"
+  echo -e "${SETUP_NGINX_FILE}"
+  echo -e "Try opening InvenTree with either\nhttp://localhost/ or http://${INVENTREE_IP}/\n"
+  echo -e "Admin user data:"
+  echo -e "   Email: ${INVENTREE_ADMIN_EMAIL}"
+  echo -e "   Username: ${INVENTREE_ADMIN_USER}"
+  echo -e "   Password: ${INVENTREE_ADMIN_PASSWORD}"
+  echo -e "####################################################################################"
+}
diff --git a/contrib/packager.io/postinstall.sh b/contrib/packager.io/postinstall.sh
new file mode 100755
index 0000000000..62efbc3b94
--- /dev/null
+++ b/contrib/packager.io/postinstall.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+#
+# packager.io postinstall script
+#
+
+exec > >(tee ${APP_HOME}/log/setup_$(date +"%F_%H_%M_%S").log) 2>&1
+
+PATH=${APP_HOME}/env/bin:${APP_HOME}/:/sbin:/bin:/usr/sbin:/usr/bin:
+
+# import functions
+. ${APP_HOME}/contrib/packager.io/functions.sh
+
+# Envs that should be passed to setup commands
+export SETUP_ENVS=PATH,APP_HOME,INVENTREE_MEDIA_ROOT,INVENTREE_STATIC_ROOT,INVENTREE_PLUGINS_ENABLED,INVENTREE_PLUGIN_FILE,INVENTREE_CONFIG_FILE,INVENTREE_SECRET_KEY_FILE,INVENTREE_DB_ENGINE,INVENTREE_DB_NAME,INVENTREE_DB_USER,INVENTREE_DB_PASSWORD,INVENTREE_DB_HOST,INVENTREE_DB_PORT,INVENTREE_ADMIN_USER,INVENTREE_ADMIN_EMAIL,INVENTREE_ADMIN_PASSWORD,SETUP_NGINX_FILE,SETUP_ADMIN_PASSWORD_FILE,SETUP_NO_CALLS,SETUP_DEBUG,SETUP_EXTRA_PIP
+
+# Get the envs
+detect_local_env
+
+# default config
+export CONF_DIR=/etc/inventree
+export DATA_DIR=${APP_HOME}/data
+# Setup variables
+export SETUP_NGINX_FILE=${SETUP_NGINX_FILE:-/etc/nginx/sites-enabled/inventree.conf}
+export SETUP_ADMIN_PASSWORD_FILE=${CONF_DIR}/admin_password.txt
+export SETUP_NO_CALLS=${SETUP_NO_CALLS:-false}
+# SETUP_DEBUG can be set to get debug info
+# SETUP_EXTRA_PIP can be set to install extra pip packages
+
+# get base info
+detect_envs
+detect_docker
+detect_initcmd
+detect_ip
+
+# create processes
+create_initscripts
+create_admin
+
+# run updates
+stop_inventree
+update_or_install
+# Write config file
+if [ "${SETUP_CONF_LOADED}" = "true" ]; then
+  set_env
+fi
+start_inventree
+
+# show info
+final_message
diff --git a/runtime.txt b/runtime.txt
new file mode 100644
index 0000000000..119ff10234
--- /dev/null
+++ b/runtime.txt
@@ -0,0 +1 @@
+python-3.10.7