Skip to content

AdGuard Home

Deploy AdGuard Home on Kubernetes — a network-wide DNS-level ad and tracker blocker. Supports DNS over HTTPS, DNS over TLS, custom rewrite rules, client-level settings, and optional multi-instance synchronization.

Two deployment modes: wizard (default) vs pre-configured — each uses a different web UI port
  • Wizard mode (config.adGuardHome: {}): AdGuard Home starts on port 3000 for the initial setup wizard. After completing setup, the UI moves to port 80.
  • Pre-configured mode (config.adGuardHome populated): Setup wizard is skipped entirely. The UI starts directly on port 80, ready for production use.

Choose pre-configured mode for GitOps or reproducible deployments. Wizard mode is suitable for first-time manual setup.

Key Features

  • Two deployment modes — interactive wizard or full pre-configured via values
  • Dual PVC — separate conf (config + TLS certs) and work (query log + stats + filters) volumes
  • Dedicated DNS service — separate LoadBalancer for UDP/TCP port 53
  • adguardhome-sync — multi-instance config synchronization (primary → replicas)
  • Full-volume backuptar archives both PVCs to S3 (no database involved)

Installation

HTTPS repository:

helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install adguard-home helmforge/adguard-home -f values.yaml

OCI registry:

helm install adguard-home oci://ghcr.io/helmforgedev/helm/adguard-home -f values.yaml

Deployment Examples

# values.yaml — AdGuard Home in wizard mode (default)
# Web UI starts on port 3000 for first-time setup
# After wizard completion, UI moves to port 80

service:
  dns:
    type: LoadBalancer
    loadBalancerIP: '192.168.1.53' # static IP for DNS (optional, MetalLB/cloud LB)

persistence:
  conf:
    enabled: true
    size: 256Mi
  work:
    enabled: true
    size: 5Gi # query log + filter lists + statistics

ingress:
  enabled: true
  ingressClassName: traefik
  hosts:
    - host: adguard.example.com
      paths:
        - path: /
          pathType: Prefix
# Access wizard for first-time setup:
kubectl port-forward svc/<release>-adguard-home-web 3000:80
# Visit http://localhost:3000
# values.yaml — AdGuard Home pre-configured (wizard skipped, UI on port 80)
config:
  adGuardHome:
    http:
      address: 0.0.0.0:80
      session_ttl: 720h
    users:
      - name: admin
        # Generate: htpasswd -bnBC 10 "" 'mypassword' | cut -d: -f2
        password: '$2y$10$...'
    dns:
      bind_hosts:
        - 0.0.0.0
      port: 53
      upstream_dns:
        - 'https://dns.cloudflare.com/dns-query'
        - 'https://dns.google/dns-query'
      bootstrap_dns:
        - 1.1.1.1
        - 8.8.8.8
      protection_enabled: true
      filtering_enabled: true
      cache_size: 4194304
    filters:
      - enabled: true
        url: 'https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt'
        name: AdGuard DNS filter
        id: 1
      - enabled: true
        url: 'https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt'
        name: AdAway Default Blocklist
        id: 2
    querylog:
      enabled: true
      interval: 720h
    statistics:
      enabled: true
      interval: 168h
    schema_version: 29

service:
  dns:
    type: LoadBalancer
    loadBalancerIP: '192.168.1.53'

persistence:
  conf:
    enabled: true
    size: 256Mi
  work:
    enabled: true
    size: 5Gi

ingress:
  enabled: true
  ingressClassName: traefik
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: adguard.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: adguard-home-tls
      hosts:
        - adguard.example.com
# values.yaml — Primary instance with adguardhome-sync to replica
# Deploy a second instance as the replica (separate helm release)
config:
  adGuardHome:
    http:
      address: 0.0.0.0:80
    users:
      - name: admin
        password: '$2y$10$...'
    schema_version: 29

sync:
  enabled: true
  origin:
    url: 'http://adguard-home.adguard.svc.cluster.local:80'
    username: admin
    password: 'admin-password' # use existingSecret in production
  replicas:
    - url: 'http://adguard-home-replica.adguard.svc.cluster.local:80'
      username: admin
      password: 'admin-password'
  cron: '*/10 * * * *' # sync every 10 minutes; empty = continuous daemon
  runOnStart: true
  features:
    dns:
      serverConfig: true
      accessLists: true
      rewrites: true
    general:
      settings: true
      protection: true
      clients: true
      filters: true

service:
  dns:
    type: LoadBalancer
    loadBalancerIP: '192.168.1.53'
# values.yaml — AdGuard Home with daily S3 backup
# Backup archives both conf and work PVCs (tar — no database)
config:
  adGuardHome:
    http:
      address: 0.0.0.0:80
    users:
      - name: admin
        password: '$2y$10$...'
    schema_version: 29

service:
  dns:
    type: LoadBalancer
    loadBalancerIP: '192.168.1.53'

persistence:
  conf:
    enabled: true
    size: 256Mi
  work:
    enabled: true
    size: 5Gi

backup:
  enabled: true
  schedule: '0 2 * * *'
  s3:
    endpoint: https://s3.amazonaws.com
    bucket: adguard-backups
    prefix: adguard-home
    existingSecret: adguard-s3-credentials # keys: access-key, secret-key

ingress:
  enabled: true
  ingressClassName: traefik
  hosts:
    - host: adguard.example.com
      paths:
        - path: /
          pathType: Prefix

Configuration Reference

Image

Parameter Type Default Description
image.repository string docker.io/adguard/adguardhome AdGuard Home image.
image.tag string "v0.107.77" Image tag.

Configuration

Parameter Type Default Description
config.adGuardHome object {} Pre-seed config rendered as AdGuardHome.yaml. Empty = wizard mode.
config.existingSecret string "" Existing secret with AdGuardHome.yaml content (key: AdGuardHome.yaml).
Generate bcrypt password hashes for config.adGuardHome.users

AdGuard Home requires bcrypt-hashed passwords in the configuration file. Generate with:

htpasswd -bnBC 10 "" 'mypassword' | cut -d: -f2

Use the output (starting with $2y$10$...) as the password value.

Persistence — Dual PVC

Parameter Type Default Description
persistence.conf.enabled boolean true Enable PVC for /opt/adguardhome/conf (config + TLS certs).
persistence.conf.size string 256Mi Conf PVC size.
persistence.conf.storageClass string "" StorageClass for conf PVC.
persistence.conf.existingClaim string "" Use an existing PVC for conf.
persistence.work.enabled boolean true Enable PVC for /opt/adguardhome/work (query log + stats + filter lists).
persistence.work.size string 2Gi Work PVC size. Increase for longer query log retention.
persistence.work.storageClass string "" StorageClass for work PVC.
persistence.work.existingClaim string "" Use an existing PVC for work.

Init Permissions

Parameter Type Default Description
initPermissions.enabled boolean true Normalize /opt/adguardhome/conf and /opt/adguardhome/work before start.
initPermissions.image.repository string docker.io/library/busybox Init container image repository.
initPermissions.image.tag string "1.37" Init container image tag.
initPermissions.securityContext.capabilities.add array [CHOWN, DAC_OVERRIDE, FOWNER] Baseline-compatible capabilities for PVC ownership, recursive traversal, and directory mode changes.
initPermissions.resources object {} Resource requests and limits for the init container.

When securityContext.runAsUser or podSecurityContext.runAsUser is configured for a non-root runtime, pre-seed AdGuardHome.yaml with config.adGuardHome or config.existingSecret. The interactive setup wizard requires administrator privileges on first launch; pre-seeded mode runs with non-root UID and podSecurityContext.fsGroup.

Services

Parameter Type Default Description
service.web.type string ClusterIP Web UI service type.
service.web.port integer 80 Web UI port.
service.dns.type string LoadBalancer DNS service type. Use NodePort for bare-metal without MetalLB.
service.dns.port integer 53 DNS service port (UDP + TCP).
service.dns.loadBalancerIP string "" Static IP for the DNS LoadBalancer.

Ingress

Parameter Type Default Description
ingress.enabled boolean false Enable Ingress for the web UI.
ingress.ingressClassName string traefik Ingress class name.
ingress.hosts array [] Host and path rules.
ingress.tls array [] TLS configuration.

Sync (adguardhome-sync)

Parameter Type Default Description
sync.enabled boolean false Deploy the adguardhome-sync sidecar.
sync.origin.url string "" URL of the origin AdGuard Home instance.
sync.origin.username string "" Admin username on the origin.
sync.origin.password string "" Admin password on the origin. Use existingSecret.
sync.replicas array [] List of replica instances (url, username, password).
sync.cron string */10 * * * * Sync schedule. Empty string = continuous daemon mode.
sync.runOnStart boolean true Run sync immediately on pod startup.
sync.existingSecret string "" Existing secret with sync credentials.
sync.features.dns.serverConfig boolean true Sync DNS server configuration.
sync.features.general.filters boolean true Sync filter lists.
sync.features.general.clients boolean true Sync client settings.

Backup

Backup archives both PVCs (conf + work) as tar archives. No database is involved.

Parameter Type Default Description
backup.enabled boolean false Enable scheduled S3 backup CronJob.
backup.schedule string "0 2 * * *" Cron schedule.
backup.archivePrefix string adguard-home Prefix for backup archive filenames.
backup.s3.endpoint string "" S3-compatible endpoint URL.
backup.s3.bucket string "" Target bucket name.
backup.s3.existingSecret string "" Existing secret with S3 credentials.
extraManifests array [] Extra Kubernetes manifests.

More Information