# Deploy Lua Custom Plugins

Custom Lua plugins extend gateway request processing when built-in plugins do not provide the required behavior. Because custom code runs inside the gateway, review and test it before deployment.

The workflow below loads the example `file-proxy` plugin, applies it with HTTPRoute or ApisixRoute, and verifies that it returns a static file.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

1. Complete [Set Up Ingress Controller and Gateway](https://docs.api7.ai/ingress-controller/set-up-ingress-controller-and-gateway.md).
2. Review the [Develop File Proxy Plugin](https://docs.api7.ai/apisix/how-to-guide/custom-plugins/create-plugin-in-lua.md#develop-file-proxy-plugin) section and save `file-proxy.lua` as an example custom plugin file. Both product paths use the same plugin code.

## Load Custom Plugin[​](#load-custom-plugin "Direct link to Load Custom Plugin")

Every gateway pod that can serve the route must load the same custom plugin code. Use the workflow for your gateway product.

* APISIX
* API7 Enterprise

Create a ConfigMap in the gateway namespace from the custom plugin file:

```
kubectl create configmap custom-file-proxy \

  --namespace aic \

  --from-file=file-proxy.lua
```

For an isolated local evaluation, upgrade the APISIX release to mount and enable the plugin:

```
helm upgrade apisix apisix/apisix \

  --version 2.18.0 \

  --namespace aic \

  --reuse-values \

  --set "apisix.customPlugins.enabled=true" \

  --set "apisix.customPlugins.plugins[0].name=file-proxy" \

  --set "apisix.customPlugins.plugins[0].configMap.name=custom-file-proxy" \

  --set "apisix.customPlugins.plugins[0].configMap.mounts[0].key=file-proxy.lua" \

  --set "apisix.customPlugins.plugins[0].configMap.mounts[0].path=/opts/custom_plugins/apisix/plugins/file-proxy.lua" \

  --set "apisix.plugins={prometheus,file-proxy}"
```

Wait for the gateway Deployment to finish restarting with the custom plugin:

```
kubectl rollout status deployment/apisix --namespace aic
```

Reduced Plugin List

The `apisix.plugins` value replaces the default HTTP plugin list. The reduced list above keeps `prometheus`, which the packaged `syslog` stream plugin requires, and is intended only for the isolated local evaluation. For an existing gateway, add `file-proxy` to its existing HTTP plugin list without removing any plugins the deployment uses.

Upload the plugin to the gateway group used by the Ingress Controller. Follow [Register the Plugin in API7 Gateway](https://docs.api7.ai/api7-gateway/how-to-guides/plugin-development/custom-lua-plugins.md#register-the-plugin-in-api7-gateway) for the Dashboard workflow.

## Test Custom Plugin[​](#test-custom-plugin "Direct link to Test Custom Plugin")

The examples below use the `aic` namespace and `apisix` Gateway created by the APISIX local evaluation. For API7 Gateway, replace the namespace and the HTTPRoute `parentRefs.name` with the values generated by API7 Dashboard.

For one-time verification, store a static `openapi.yaml` file on the gateway pod. The APISIX local evaluation has one gateway replica and uses the `apisix` Deployment:

```
kubectl exec -i --namespace aic deployment/apisix \

  -- sh -c 'cat > /usr/local/apisix/openapi.yaml' <<'EOF'

openapi: 3.0.1

info:

  title: OpenAPI Spec

  description: OpenAPI Spec file description.

EOF
```

For API7 Gateway or a deployment with multiple replicas, make the file available at the same path on every gateway pod through your deployment configuration. Writing the file to one pod does not make it available to the other replicas.

Create a route with the custom plugin that uses the `openapi.yaml` file:

* Gateway API
* APISIX CRD

file-proxy-route.yaml

```
apiVersion: v1

kind: Service

metadata:

  namespace: aic

  name: file-proxy-backend

spec:

  type: ExternalName

  externalName: httpbin.org

  ports:

  - name: http

    port: 80

---

apiVersion: apisix.apache.org/v1alpha1

kind: PluginConfig

metadata:

  namespace: aic

  name: file-proxy-plugin-config

spec:

  plugins:

    - name: file-proxy

      config:

        path: /usr/local/apisix/openapi.yaml

---

apiVersion: gateway.networking.k8s.io/v1

kind: HTTPRoute

metadata:

  namespace: aic

  name: openapi-file-proxy

spec:

  parentRefs:

  - name: apisix

  rules:

  - matches: 

    - path:

        type: Exact

        value: /openapi.yaml

    filters:

    - type: ExtensionRef

      extensionRef:

        group: apisix.apache.org

        kind: PluginConfig

        name: file-proxy-plugin-config

    backendRefs:

    - name: file-proxy-backend

      port: 80
```

The shared HTTPRoute includes a backend because APISIX Ingress Controller generates a `500` fallback for a rule without one. API7 Ingress Controller 2.2.1 accepts the plugin-only shape, but the backend keeps the example portable across both products. The `file-proxy` plugin returns the file before the gateway sends a request to the fallback backend.

file-proxy-route.yaml

```
apiVersion: apisix.apache.org/v2

kind: ApisixRoute

metadata:

  namespace: aic

  name: openapi-file-proxy

spec:

  ingressClassName: apisix

  http:

    - name: openapi-file-proxy

      match:

        paths:

          - /openapi.yaml

      plugins:

      - name: file-proxy

        enable: true

        config:

          path: /usr/local/apisix/openapi.yaml
```

The plugin returns the response itself, so this plugin-only ApisixRoute does not require a backend.

Apply the configuration to your cluster:

```
kubectl apply -f file-proxy-route.yaml
```

For the APISIX local evaluation, expose the gateway Service on your local machine. For an API7 installation, substitute the gateway Service generated by API7 Dashboard.

```
kubectl port-forward --namespace aic svc/apisix-gateway 9080:80 &
```

Send a request to the route:

```
curl "http://127.0.0.1:9080/openapi.yaml"
```

You should receive a response with the content of the file `openapi.yaml`:

```
openapi: 3.0.1

info:

  title: OpenAPI Spec

  description: OpenAPI Spec file description.
```

This verifies that the custom plugin is loaded and working correctly.
