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

ParameterTypeDefaultDescription
image.repositorystringdocker.io/adguard/adguardhomeAdGuard Home image.
image.tagstring"v0.107.76"Image tag.

Configuration

ParameterTypeDefaultDescription
config.adGuardHomeobject{}Pre-seed config rendered as AdGuardHome.yaml. Empty = wizard mode.
config.existingSecretstring""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

ParameterTypeDefaultDescription
persistence.conf.enabledbooleantrueEnable PVC for /opt/adguardhome/conf (config + TLS certs).
persistence.conf.sizestring256MiConf PVC size.
persistence.conf.storageClassstring""StorageClass for conf PVC.
persistence.conf.existingClaimstring""Use an existing PVC for conf.
persistence.work.enabledbooleantrueEnable PVC for /opt/adguardhome/work (query log + stats + filter lists).
persistence.work.sizestring2GiWork PVC size. Increase for longer query log retention.
persistence.work.storageClassstring""StorageClass for work PVC.
persistence.work.existingClaimstring""Use an existing PVC for work.

Init Permissions

ParameterTypeDefaultDescription
initPermissions.enabledbooleantrueNormalize /opt/adguardhome/conf and /opt/adguardhome/work before start.
initPermissions.image.repositorystringdocker.io/library/busyboxInit container image repository.
initPermissions.image.tagstring"1.37"Init container image tag.
initPermissions.securityContext.capabilities.addarray[CHOWN, DAC_OVERRIDE, FOWNER]Baseline-compatible capabilities for PVC ownership, recursive traversal, and directory mode changes.
initPermissions.resourcesobject{}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

ParameterTypeDefaultDescription
service.web.typestringClusterIPWeb UI service type.
service.web.portinteger80Web UI port.
service.dns.typestringLoadBalancerDNS service type. Use NodePort for bare-metal without MetalLB.
service.dns.portinteger53DNS service port (UDP + TCP).
service.dns.loadBalancerIPstring""Static IP for the DNS LoadBalancer.

Ingress

ParameterTypeDefaultDescription
ingress.enabledbooleanfalseEnable Ingress for the web UI.
ingress.ingressClassNamestringtraefikIngress class name.
ingress.hostsarray[]Host and path rules.
ingress.tlsarray[]TLS configuration.

Sync (adguardhome-sync)

ParameterTypeDefaultDescription
sync.enabledbooleanfalseDeploy the adguardhome-sync sidecar.
sync.origin.urlstring""URL of the origin AdGuard Home instance.
sync.origin.usernamestring""Admin username on the origin.
sync.origin.passwordstring""Admin password on the origin. Use existingSecret.
sync.replicasarray[]List of replica instances (url, username, password).
sync.cronstring*/10 * * * *Sync schedule. Empty string = continuous daemon mode.
sync.runOnStartbooleantrueRun sync immediately on pod startup.
sync.existingSecretstring""Existing secret with sync credentials.
sync.features.dns.serverConfigbooleantrueSync DNS server configuration.
sync.features.general.filtersbooleantrueSync filter lists.
sync.features.general.clientsbooleantrueSync client settings.

Backup

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

ParameterTypeDefaultDescription
backup.enabledbooleanfalseEnable scheduled S3 backup CronJob.
backup.schedulestring"0 2 * * *"Cron schedule.
backup.archivePrefixstringadguard-homePrefix for backup archive filenames.
backup.s3.endpointstring""S3-compatible endpoint URL.
backup.s3.bucketstring""Target bucket name.
backup.s3.existingSecretstring""Existing secret with S3 credentials.
extraManifestsarray[]Extra Kubernetes manifests.

More Information