{"id":50287377,"url":"https://github.com/jamesagarside/elastic-airgapped","last_synced_at":"2026-05-28T03:30:29.120Z","repository":{"id":352866090,"uuid":"1216925075","full_name":"jamesagarside/elastic-airgapped","owner":"jamesagarside","description":"A project for easily deploying Elastic locally using Elastic Cloud Kubernetes and Docker Desktop which will function with no internet. ","archived":false,"fork":false,"pushed_at":"2026-05-26T07:06:36.000Z","size":53,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-26T09:11:57.964Z","etag":null,"topics":["airgapped","eck","elastic","elastic-cloud-kubernetes","elastic-stack","elasticsearch","kibana","kubernetes"],"latest_commit_sha":null,"homepage":"https://jamesagarside.github.io/elastic-airgapped/","language":"Makefile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesagarside.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-21T11:19:05.000Z","updated_at":"2026-04-25T11:24:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesagarside/elastic-airgapped","commit_stats":null,"previous_names":["jamesagarside/elastic-airgapped"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jamesagarside/elastic-airgapped","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesagarside%2Felastic-airgapped","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesagarside%2Felastic-airgapped/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesagarside%2Felastic-airgapped/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesagarside%2Felastic-airgapped/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesagarside","download_url":"https://codeload.github.com/jamesagarside/elastic-airgapped/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesagarside%2Felastic-airgapped/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33593400,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["airgapped","eck","elastic","elastic-cloud-kubernetes","elastic-stack","elasticsearch","kibana","kubernetes"],"created_at":"2026-05-28T03:30:28.558Z","updated_at":"2026-05-28T03:30:29.112Z","avatar_url":"https://github.com/jamesagarside.png","language":"Makefile","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Elastic Airgapped\n\n[![License](https://img.shields.io/github/license/jamesagarside/elastic-airgapped)](LICENSE)\n[![Latest release](https://img.shields.io/github/v/release/jamesagarside/elastic-airgapped)](https://github.com/jamesagarside/elastic-airgapped/releases/latest)\n[![GitHub stars](https://img.shields.io/github/stars/jamesagarside/elastic-airgapped?style=social)](https://github.com/jamesagarside/elastic-airgapped/stargazers)\n[![Last commit](https://img.shields.io/github/last-commit/jamesagarside/elastic-airgapped)](https://github.com/jamesagarside/elastic-airgapped/commits)\n[![Elastic Stack](https://img.shields.io/badge/Elastic-9.3.1-005571?logo=elastic\u0026logoColor=white)](https://www.elastic.co/)\n[![ECK](https://img.shields.io/badge/ECK-Cloud_on_Kubernetes-005571?logo=elastic\u0026logoColor=white)](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html)\n[![Kubernetes](https://img.shields.io/badge/Kubernetes-326CE5?logo=kubernetes\u0026logoColor=white)](https://kubernetes.io/)\n[![Docker Desktop](https://img.shields.io/badge/Docker%20Desktop-2496ED?logo=docker\u0026logoColor=white)](https://www.docker.com/products/docker-desktop/)\n\n**A fully air-gapped Elastic Stack lab: deploy Elasticsearch, Kibana, Fleet, Logstash, and Maps on Docker Desktop Kubernetes with zero internet required at run time.**\n\nElastic Airgapped is a single-laptop lab for engineers who need the full Elastic Stack offline: demos on a plane, field work without connectivity, secure-environment testing, or just a reproducible local cluster that does not reach out to the internet once deployed. Pull every asset once — images, Helm charts, Elastic Cloud on Kubernetes (ECK) manifests, GeoIP databases, ML models, and the Fleet package registry — then deploy and run with the network off.\n\n---\n\n## Table of Contents\n\n- [Why Elastic Airgapped?](#why-elastic-airgapped)\n- [Features](#features)\n- [Quick Start](#quick-start)\n- [Prerequisites](#prerequisites)\n- [Pull Assets While Online](#pull-assets-while-online)\n- [Deploy Offline](#deploy-offline)\n- [License Tiers](#license-tiers-trial--basic--enterprise)\n- [Access the Stack](#access-the-stack)\n- [Makefile Reference](#makefile-reference)\n- [Project Structure](#project-structure)\n- [Architecture](#architecture)\n- [Air-gap Considerations](#air-gap-considerations)\n- [Troubleshooting](#troubleshooting)\n- [FAQ](#faq)\n\n---\n\n## Why Elastic Airgapped?\n\nRunning Elasticsearch locally is easy; running the **full** stack without internet is not. Fleet, integrations, ML models, GeoIP databases, and the ECK operator all reach out to `elastic.co` endpoints by default. A real air-gapped lab has to cache every one of those dependencies and rewire the cluster to use the local copies.\n\nThis project does all of that for you with a single `make pull-all` while you are online, then lets you run the stack end-to-end while offline.\n\n**Who this is for:** solutions engineers preparing offline demos, security teams validating Elastic in disconnected environments, developers on unreliable connectivity, and anyone who wants a reproducible, self-contained local Elastic cluster.\n\n## Features\n\n- **Full stack**: Elasticsearch (with ML node), Kibana, Fleet Server, Elastic Agent, Logstash, Elastic Maps Server.\n- **True air-gap**: GeoIP auto-download disabled, local Elastic Package Registry (EPR) for Fleet integrations, pre-cached ML models (ELSER).\n- **ECK-managed**: Elastic Cloud on Kubernetes operator handles lifecycle, upgrades, certificates, and cluster bootstrapping.\n- **Docker Desktop Kubernetes**: no extra cluster to manage — uses the Kubernetes that ships with Docker Desktop for Mac or Windows.\n- **License aware**: switch between **Trial**, **Basic**, and **Enterprise** tiers with a single `.env` variable. Enterprise accepts a license JSON file.\n- **One-command deploy**: `make deploy` provisions namespaces, installs the ECK operator, activates your license, and rolls out the stack.\n- **One-command cleanup**: `make clean-all` removes the stack, the operator, the ingress controller, and leaves your Docker daemon otherwise untouched.\n- **Portable image cache**: `make save-images` writes every required container image to `assets/images/*.tar` so the asset bundle can be transported to another machine.\n- **Ingress + TLS**: ingress-nginx with `*.localhost` hostnames that resolve without touching `/etc/hosts` on Chrome and Firefox. Safari gets a `make add-hosts` helper.\n- **Optional local LLM**: LM Studio is started, loaded, and stopped by `make deploy` / `make clean-all`, and a pre-wired Kibana AI Connector targets it on the host — so the Kibana AI Assistant works with a private model and no internet, with the same lifecycle commands as the rest of the stack.\n\n---\n\n## Quick Start\n\nDocker Desktop installed and Kubernetes enabled? Run:\n\n```bash\n# 1. Clone\ngit clone https://github.com/jamesagarside/elastic-airgapped.git\ncd elastic-airgapped\n\n# 2. Copy the env template\ncp .env.example .env\n\n# 3. (Online) Cache every dependency\nmake pull-all\n\n# 4. (Offline is fine now) Deploy\nmake deploy\n\n# 5. Add /etc/hosts entries for Safari (Chrome/Firefox skip this)\nmake add-hosts\n\n# 6. Open\nopen https://kibana.localhost\n```\n\nUsername: `elastic`. Password: retrieve with `kubectl get secret elastic-lab-es-elastic-user -n elastic -o go-template='{{.data.elastic | base64decode}}'`.\n\n---\n\n## Prerequisites\n\nYou need:\n\n- **Docker Desktop** (macOS, Windows, or Linux) with **Kubernetes enabled**.\n- **At least 12 GB of RAM allocated to Docker Desktop** (Settings -\u003e Resources). Elasticsearch's ML node alone asks for 4 GB.\n- **`containerd` image store enabled** in Docker Desktop (Settings -\u003e General -\u003e *Use containerd for pulling and storing images*). Without it, you also need `kind` installed and must run `make load-images` after `make pull-images`.\n- **`kubectl`** on `PATH`.\n- **Homebrew** (macOS) — `make check-tools` will install `helm` and `gettext` if they are missing.\n- **Internet** for the initial `make pull-all` only. Everything after that runs offline.\n\nVerify your environment:\n\n```bash\nmake check-env\nmake show-config\n```\n\n---\n\n## Pull Assets While Online\n\nOne command downloads every asset the stack needs at run time:\n\n```bash\nmake pull-all\n```\n\nThis runs in sequence:\n\n| Step | What it pulls | Destination |\n| ---- | ------------- | ----------- |\n| `pull-assets` | ECK CRDs + operator YAML, ingress-nginx Helm chart | `assets/eck/`, `assets/charts/` |\n| `pull-geoip` | GeoLite2 City / Country / ASN `.mmdb` databases | `assets/geoip/` |\n| `pull-ml-models` | ELSER v2 tokenizer + model weights | `assets/ml-models/` |\n| `pull-epr` | Elastic Package Registry distribution image | local Docker daemon |\n| `pull-images` | All stack images: Elasticsearch, Kibana, Agent, Logstash, Maps, ECK operator, ingress-nginx | local Docker daemon |\n\n**Want a portable bundle** (for moving to a different offline machine)?\n\n```bash\nmake save-images      # writes assets/images/*.tar\n```\n\nThen transport the whole `assets/` directory to the target machine and run `make load-images-tarball` before `make deploy`.\n\nVerify the cache before disconnecting:\n\n```bash\nmake verify-offline\n```\n\n---\n\n## Deploy Offline\n\n```bash\nmake deploy\n```\n\n`make deploy` will:\n\n1. Validate `.env` (including license tier).\n2. Install the ECK operator from `assets/eck/`.\n3. Apply the license you selected in `.env` (Trial, Basic, or Enterprise).\n4. Install ingress-nginx from the local Helm chart.\n5. Start LM Studio on the host and load `LLM_CONNECTOR_MODEL` (skipped if that variable is empty in `.env`; LM Studio is installed via Homebrew if not already present).\n6. Apply every manifest under `manifests/`, substituting `.env` values via `envsubst`.\n7. Wait for Elasticsearch to report `health: green`.\n\n---\n\n## License Tiers (Trial / Basic / Enterprise)\n\nSet the license tier in `.env`:\n\n```bash\n# One of: trial | basic | enterprise\nECK_LICENSE_TIER=trial\n# Only used when ECK_LICENSE_TIER=enterprise\nECK_LICENSE_FILE=/path/to/eck-enterprise-license.json\n```\n\n| Tier | What you get | How it is applied |\n| ---- | ------------ | ----------------- |\n| **trial** | 30 days of Enterprise features (ML, alerts, AI Assistant, etc.). Can be started **once per cluster**. | `kubectl apply` of a Secret labelled `license.k8s.elastic.co/type: enterprise_trial`. |\n| **basic** | Free tier. No action required. Any existing trial/enterprise secret is removed. | `kubectl delete secret eck-trial-license eck-license` in the ECK namespace. |\n| **enterprise** | Full, paid Enterprise features. | `kubectl create secret generic eck-license --from-file=\u003cLICENSE_FILE\u003e` with `license.k8s.elastic.co/scope=operator`. |\n\nApply the license independently of `make deploy`:\n\n```bash\nmake apply-license     # applies per ECK_LICENSE_TIER\nmake check-license     # shows current license secret + elastic-licensing ConfigMap\nmake clean-license     # removes all license secrets (reverts to basic)\n```\n\nSee the [official ECK licensing docs](https://www.elastic.co/docs/deploy-manage/license/manage-your-license-in-eck) for the source material.\n\n---\n\n## Access the Stack\n\nDefault hostnames (set in `.env`):\n\n| Service | URL | Notes |\n| ------- | --- | ----- |\n| Kibana  | `https://kibana.localhost` | main UI |\n| Elasticsearch | `https://elasticsearch.localhost` | REST API |\n\nGet the `elastic` user password:\n\n```bash\nkubectl get secret elastic-lab-es-elastic-user \\\n  -n elastic \\\n  -o go-template='{{.data.elastic | base64decode}}'\n```\n\nTail operator or stack logs:\n\n```bash\nkubectl -n elastic-system logs -l control-plane=elastic-operator -f\nkubectl -n elastic get elasticsearch,kibana,agent,logstash,elasticmapsserver\n```\n\n---\n\n## Makefile Reference\n\n```\nmake help                   # list every target\n\n# Online, one-time\nmake pull-all               # assets + images + GeoIP + ML models + EPR\nmake save-images            # export images to assets/images/*.tar (for portability)\n\n# Offline\nmake deploy                 # full deploy (ECK -\u003e license -\u003e ingress -\u003e LLM -\u003e stack)\nmake apply-license          # re-apply license per .env\nmake add-hosts              # /etc/hosts entries for Safari\n\n# Local LLM (optional, host-side via LM Studio)\nmake check-lms              # ensure the lms CLI is installed (installs LM Studio via Homebrew if missing)\nmake start-llm              # start LM Studio server and load $LLM_CONNECTOR_MODEL\nmake check-llm              # probe the endpoint and confirm the model is loaded\nmake stop-llm               # unload models and stop the LM Studio server\n\n# Inspect\nmake show-config            # pretty-print resolved .env\nmake check-env              # validate .env\nmake diff-env               # .env vs .env.example\nmake verify-offline         # are all asset caches present?\nmake check-license          # current license state\n\n# Teardown\nmake clean-elastic          # remove stack (keeps ECK operator)\nmake clean-ingress          # remove ingress-nginx\nmake clean-eck              # remove ECK operator + CRDs\nmake clean-all              # all of the above + stop LM Studio\n```\n\n---\n\n## Project Structure\n\n```text\nelastic-airgapped/\n├── .env.example                    # config template\n├── Makefile                        # every workflow lives here\n├── manifests/                      # Kubernetes manifests (envsubst-templated)\n│   ├── elasticsearch.yaml\n│   ├── kibana.yaml\n│   ├── fleet-server.yaml\n│   ├── agent.yaml\n│   ├── logstash.yaml\n│   ├── maps-server.yaml\n│   ├── package-registry.yaml       # local EPR (air-gapped Fleet integrations)\n│   ├── ingress.yaml\n│   └── network-policy.yaml\n├── assets/                         # populated by `make pull-all` (gitignored)\n│   ├── eck/                        # CRDs + operator.yaml\n│   ├── charts/                     # ingress-nginx helm chart (.tgz)\n│   ├── images/                     # optional: `make save-images` tarballs\n│   ├── geoip/                      # GeoLite2 .mmdb files\n│   └── ml-models/                  # ELSER model artefacts\n└── .github/workflows/              # CI, Pages, release tracking\n```\n\n---\n\n## Architecture\n\n- **Orchestration**: ECK operator in `elastic-system` watches the custom resources (`Elasticsearch`, `Kibana`, `Agent`, `Logstash`, `ElasticMapsServer`) in the `elastic` namespace and reconciles them.\n- **Ingress**: ingress-nginx terminates HTTPS for `kibana.localhost` and `elasticsearch.localhost`, routing through the cluster to the stack services.\n- **Fleet**: Fleet Server runs as an ECK-managed `Agent` with `fleetServerEnabled: true`. A single ingest Agent enrols against it.\n- **Package Registry**: a local `distribution:lite` EPR is deployed inside the cluster; Kibana is configured with `xpack.fleet.registryUrl` pointing at the in-cluster Service, so Fleet integrations install without reaching `epr.elastic.co`.\n- **GeoIP**: `ingest.geoip.downloader.enabled: false` is set in the Elasticsearch config, and the `make pull-geoip` target downloads GeoLite2 databases into `assets/geoip/` for optional upload via the GeoIP processor API.\n- **ML models**: ELSER model artefacts are cached in `assets/ml-models/` and can be uploaded to the ML node once the cluster is up.\n- **LLM (optional)**: a Kibana AI Connector is wired to an OpenAI-compatible endpoint on the host (LM Studio by default) via `host.docker.internal`. Apple Silicon's GPU is not visible to Docker Desktop's Kubernetes VM, so the model stays on the host (Metal/MLX) while `make deploy` / `make clean-all` drive its lifecycle through the `lms` CLI — keeping the cluster reproducible and the inference fast. Fully offline and private.\n\n---\n\n## Air-gap Considerations\n\nWhat \"air-gapped\" actually means here:\n\n| Concern | Status |\n| ------- | ------ |\n| Container images | Pulled to local Docker daemon; optionally saved to tarball for portability. |\n| ECK operator manifests + CRDs | Cached in `assets/eck/`. |\n| Ingress Helm chart | Cached in `assets/charts/`. |\n| GeoIP auto-download | Disabled in Elasticsearch config. Databases cached in `assets/geoip/` for optional manual upload. |\n| Fleet integrations | Served by a local Elastic Package Registry deployed inside the cluster. |\n| ML models (ELSER) | Cached in `assets/ml-models/` and uploadable via the ML trained-models API. |\n| Detection rule updates | Auto-updates are an internet call; disable in Kibana or ignore on an offline lab. |\n| Endpoint artefact updates | Same — manual or disabled. |\n| Enterprise license | User-supplied JSON, applied as a Kubernetes Secret. |\n\n---\n\n## Troubleshooting\n\n### Pods stuck Pending\n\n```bash\nkubectl describe pod -n elastic \u003cpod\u003e\n```\n\nUsually means Docker Desktop has not allocated enough memory. Bump it in Settings -\u003e Resources.\n\n### ImagePullBackOff\n\n```bash\nkubectl get events -n elastic --sort-by=.lastTimestamp | tail\n```\n\nIf you see `ErrImagePull`, images are not in the local Docker daemon. Either you did not run `make pull-images`, or Docker Desktop is not using the containerd image store. Enable containerd (Settings -\u003e General) or run `make load-images` to push images into a `kind` node instead.\n\n### Ingress webhook TLS error on first deploy\n\n`make deploy` already retries six times with a 10-second backoff — the ingress-nginx admission webhook needs a few seconds to come up. If it still fails, re-run `make deploy`.\n\n### Stuck namespace on `make clean-*`\n\nThe clean targets strip finalizers and force-delete automatically. If a namespace still lingers:\n\n```bash\nkubectl patch namespace elastic \\\n  --type=merge -p '{\"metadata\":{\"finalizers\":[]}}'\n```\n\n### `.env` drifted from `.env.example`\n\n```bash\nmake diff-env\n```\n\nShows missing and stale keys, with each key's default value.\n\n---\n\n## FAQ\n\n### Does this really work with no internet?\n\nYes, once `make pull-all` has run. The runtime cluster has no outbound traffic: GeoIP auto-download is disabled, Fleet uses a local Elastic Package Registry, and every image is already in your Docker daemon. The only caveat is that the Docker Desktop daemon itself needs to be running — Docker Desktop's update check is internal to the Docker process, not the cluster.\n\n### Can I use this on Linux or Windows?\n\nYes. Docker Desktop for Mac and Windows both work. On Linux you can use Docker Desktop for Linux or adapt the Makefile to use `kind` directly (the `load-images` target already exists for that).\n\n### How much RAM do I need?\n\n12 GB allocated to Docker Desktop is the realistic minimum for the full stack (Elasticsearch x2, ML node, Kibana, Fleet, Agent, Logstash, Maps, EPR). You can reduce the ML node to 0 replicas in `manifests/elasticsearch.yaml` if you do not need ML, which drops the requirement to roughly 8 GB.\n\n### Do I need a license?\n\nNo. The default tier is **trial** (30 days of Enterprise features). After the trial you can continue on **Basic**, which is the free tier and covers Elasticsearch, Kibana, and Fleet. **Enterprise** is only needed for paid features like the full AI Assistant, Watcher, document-level security, and cross-cluster replication.\n\n### Does Fleet work offline?\n\nYes — that is the whole point of the local Elastic Package Registry deployment. Fleet in Kibana browses integrations from the in-cluster EPR, not from `epr.elastic.co`. New integrations ship with each EPR image tag, so pulling a fresh image is how you update the integration catalogue in your offline lab.\n\n### Will ML / ELSER work offline?\n\nThe ML node runs offline with no problem. Pre-trained models (ELSER, E5) normally download from `ml-models.elastic.co` on first use — `make pull-ml-models` caches those artefacts locally for manual upload via the `PUT _ml/trained_models` API.\n\n### Can I transport the asset bundle to a different machine?\n\nYes. Run `make pull-all \u0026\u0026 make save-images`, copy the `assets/` directory to the target machine, and run `make load-images-tarball \u0026\u0026 make deploy`.\n\n### How do I upgrade the stack version?\n\nBump `ELASTIC_VERSION` in `.env`, run `make pull-all` online (to cache the new images), then re-run `make deploy` offline. ECK handles rolling upgrades.\n\n### How does the local LLM integration work?\n\nKibana is templated with an AI Connector pointing at `host.docker.internal:\u003cLM_STUDIO_PORT\u003e/v1` — an OpenAI-compatible endpoint. The model itself runs on the host through LM Studio so it can use Metal/MLX (Docker Desktop's Kubernetes VM cannot see the Apple Silicon GPU), and the lab manages its lifecycle through the same Makefile as the rest of the stack:\n\n- `make deploy` runs `make start-llm`, which calls `make check-lms` to install LM Studio via Homebrew if needed (`brew install --cask lm-studio` + `lms bootstrap`), starts the `lms` server on `LM_STUDIO_PORT`, loads `LLM_CONNECTOR_MODEL`, and probes the endpoint with `make check-llm`.\n- `make clean-all` runs `make stop-llm`, which unloads models and stops the `lms` server.\n- `LLM_CONNECTOR_MODEL` in `.env` is the **single source of truth** for the model name: it is passed to `lms load` and substituted into the Kibana connector's `defaultModel`, so changing it in one place updates both sides.\n- Set `LLM_CONNECTOR_MODEL=` (empty) in `.env` to skip the LLM entirely — `make deploy` becomes a no-op for the LM Studio steps.\n\nThe Kibana AI Assistant then uses that local endpoint without ever leaving your machine.\n\n### Why ECK instead of Docker Compose?\n\nECK is how Elastic officially orchestrates the stack on Kubernetes. It handles TLS, upgrades, node roles, scaling, and stack version upgrades natively. Docker Compose works too — see the sibling project [elastic-at-home](https://github.com/jamesagarside/elastic-at-home) for a Compose-based home SIEM.\n\n---\n\n## License\n\nApache 2.0. See [LICENSE](LICENSE).\n\n## Contributing\n\nIssues and PRs welcome at [github.com/jamesagarside/elastic-airgapped](https://github.com/jamesagarside/elastic-airgapped).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesagarside%2Felastic-airgapped","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesagarside%2Felastic-airgapped","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesagarside%2Felastic-airgapped/lists"}