Andrew Mercer
on this page

This is adapted from Elastic's local development quickstart script (start-local), with Logstash and Filebeat added. It runs a single node with HTTP TLS disabled, so it's for development and homelab use, not production.

Secrets: .env

Compose reads .env from the project directory automatically. Keep it out of git.

# .env
STACK_VERSION=9.1.5
ELASTIC_PASSWORD=[ elastic_password ]
KIBANA_PASSWORD=[ kibana_system_password ]
KIBANA_ENCRYPTION_KEY=[ encryption_key ]

Generate the encryption key (Kibana needs at least 32 characters, and openssl rand -hex 16 gives exactly 32):

openssl rand -hex 16

compose.yaml

services:

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
    container_name: elasticsearch
    ports:
      - 127.0.0.1:9200:9200
    environment:
      - discovery.type=single-node
      - cluster.name=elasticsearch
      - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
      - xpack.security.enabled=true
      - xpack.security.http.ssl.enabled=false
      - xpack.license.self_generated.type=trial   # 30-day trial; reverts to basic afterwards
      - xpack.ml.use_auto_machine_memory_percent=true
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    volumes:
      - elasticsearch:/usr/share/elasticsearch/data
    ulimits:
      memlock:
        soft: -1
        hard: -1
    healthcheck:
      test: ["CMD-SHELL", "curl -s -o /dev/null --fail -u elastic:${ELASTIC_PASSWORD} http://localhost:9200"]
      interval: 10s
      timeout: 10s
      retries: 30
    networks:
      - logging-net

  # One-shot job: set the kibana_system password so Kibana can log in.
  kibana_settings:
    image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
    container_name: kibana-settings
    restart: "no"
    depends_on:
      elasticsearch:
        condition: service_healthy
    command: >
      bash -c '
        echo "Setting kibana_system password";
        until curl -s -u "elastic:${ELASTIC_PASSWORD}" -X POST
          http://elasticsearch:9200/_security/user/kibana_system/_password
          -H "Content-Type: application/json"
          -d "{\"password\":\"${KIBANA_PASSWORD}\"}" | grep -q "^{}"; do
          sleep 2;
        done;
        echo "Done.";
      '
    networks:
      - logging-net

  kibana:
    image: docker.elastic.co/kibana/kibana:${STACK_VERSION}
    container_name: kibana
    depends_on:
      kibana_settings:
        condition: service_completed_successfully
    ports:
      - 127.0.0.1:5601:5601
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      - ELASTICSEARCH_USERNAME=kibana_system
      - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
      - SERVER_PUBLICBASEURL=http://localhost:5601
      - XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY}
      - XPACK_REPORTING_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY}
      - XPACK_SECURITY_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY}
    healthcheck:
      test: ["CMD-SHELL", "curl -s -o /dev/null --fail http://localhost:5601/login"]
      interval: 10s
      timeout: 10s
      retries: 60
    networks:
      - logging-net

  logstash:
    image: docker.elastic.co/logstash/logstash:${STACK_VERSION}
    container_name: logstash
    depends_on:
      elasticsearch:
        condition: service_healthy
    volumes:
      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro
    ports:
      - "5044:5044"       # Beats
      - "5514:5514/udp"   # Syslog (e.g. OPNsense)
      - "5514:5514/tcp"   # Syslog over TCP
    environment:
      - ELASTICSEARCH_HOST=http://elasticsearch:9200   # referenced from logstash.conf
      - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
    networks:
      - logging-net

  filebeat:
    image: docker.elastic.co/beats/filebeat:${STACK_VERSION}
    container_name: filebeat
    user: root            # needed to read /var/lib/docker/containers
    restart: unless-stopped
    command: ["filebeat", "-e", "--strict.perms=false", "-c", "/usr/share/filebeat/filebeat.yml"]
    volumes:
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/log:/var/log:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - logging-net

networks:
  logging-net:

volumes:
  elasticsearch:

Notes:

  • Ports are bound to 127.0.0.1. Change them to 0.0.0.0 only if other hosts need to reach the stack, and put a reverse proxy with TLS in front if you do.
  • Kibana waits for kibana_settings to finish, so it never starts with an unset kibana_system password.
  • -e makes Filebeat log to stderr (visible in docker logs filebeat). Where events go is set in filebeat.yml.

Bring it up

docker compose up -d
docker compose ps
curl -u "elastic:$ELASTIC_PASSWORD" http://localhost:9200

Kibana is at http://localhost:5601. Log in as elastic.

Reset a password

docker exec -it elasticsearch bin/elasticsearch-reset-password -u kibana_system

If you reset kibana_system, update KIBANA_PASSWORD in .env and run docker compose up -d kibana.