Skip to content

Installing an HTTP server#

In airgapped environments, child cluster nodes download the k0s binary from a URL configured in the ClusterTemplate. That URL propagates into k0sControllerConfig bootstrap data and is used by CAPI controllers during node bootstrapping. The URL must point to a stable, statically addressable endpoint.

Note

This guide describes how to deploy a dedicated HTTP file server on the management cluster when no suitable file server is already available in your environment. Production environments commonly have existing infrastructure for this purpose — an internal Artifactory instance, an S3-compatible object store, an nginx or Apache server, or a corporate file share exposed over HTTP/HTTPS. Check with your infrastructure or platform team before following this guide. If a suitable file server is already reachable from your child cluster node networks, you only need to place the k0s binary in the appropriate directory and configure its URL during install by setting the Mirantis k0rdent Enterprise helm chart value controller.globalK0sURL.

Prerequisites#

Before proceeding, ensure the following:

  • A running Mirantis k0rdent Enterprise management cluster.
  • The registry.local/nginx:1.30.2 container image is available to the management cluster.
  • A StorageClass is available in the management cluster. Run kubectl get storageclass to verify.
  • A load balancer controller that provisions Service resources of type LoadBalancer (e.g., MetalLB, or the load balancer provider for your infrastructure platform).

Warning

The nginx:1.30.2 container image is not included in the Mirantis k0rdent Enterprise airgap bundle. Push it to your local image registry as registry.local/nginx:1.30.2 (replacing registry.local with your actual registry hostname) before deploying the file server.

Operational considerations#

  • Firewall rules: Open port 80 from child cluster node network ranges to the load balancer IP.
  • The file server uses a PersistentVolumeClaim for binary storage. The binary survives pod restarts, rescheduling, and management cluster restarts as long as the underlying PersistentVolume is intact.
  • When upgrading k0s, add the new binary to the existing binary storage without overwriting the old one — clusters currently being provisioned may still be downloading it. See Updating k0s binaries on the file server.
  • For HTTPS file server endpoints with a custom CA certificate, also set controller.k0sURLCertSecret to the name of a Secret containing ca.crt. See Configuring a global k0s URL for details.

Preparing storage for the k0s file server#

Two storage options are available:

  • PVC (recommended): Use a PersistentVolumeClaim backed by a StorageClass. The binary survives pod restarts, rescheduling, and management cluster restarts as long as the underlying PersistentVolume is intact. Requires a StorageClass in the management cluster.
  • hostPath (lab/single-node only): Place the binary directly on the node filesystem. Simple to set up, but not resilient — the binary is lost if the pod reschedules to a different node.

Save the following as k0s-fileserver-pvc.yaml. Replace your-storage-class with your StorageClass name (run kubectl get storageclass to list available options) and adjust storage size as needed:

---
apiVersion: v1
kind: Namespace
metadata:
  name: k0s-fileserver
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: k0s-fileserver-data
  namespace: k0s-fileserver
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: <your-storage-class>  # Replace with your StorageClass name (kubectl get storageclass)

Apply:

kubectl apply -f k0s-fileserver-pvc.yaml

Verify the PVC is bound before proceeding:

kubectl get pvc k0s-fileserver-data -n k0s-fileserver
NAME                   STATUS   VOLUME                   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
k0s-fileserver-data    Bound    pvc-a1b2c3d4-...         10Gi       RWO            standard       30s

Single-node or lab environments#

Warning

This approach is not suitable for production. A hostPath volume is tied to a specific node. If the pod reschedules or the node is reprovisioned, the binary is lost and all subsequent child cluster deployments will fail.

If you are running a single-node management cluster in a lab or development environment without a StorageClass, skip the PVC steps above and instead place the binary directly on the node:

sudo mkdir -p /var/lib/k0s-fileserver/k0rdent-enterprise
sudo cp k0s-v1.35.1+k0s.1-amd64 /var/lib/k0s-fileserver/k0rdent-enterprise/

Then replace the volumes.files entry in the fileserver deployment manifest with:

- name: files
  hostPath:
    path: /var/lib/k0s-fileserver
    type: DirectoryOrCreate

Load the k0s binary#

Note

The busybox:1.36.1 image is included in the Mirantis k0rdent Enterprise airgap bundle. Use the image reference with your local registry prefix, for example registry.local/k0rdent-enterprise/busybox:1.36.1. Replace registry.local with your actual registry hostname.

Save the following as k0s-binary-loader.yaml:

---
apiVersion: v1
kind: Pod
metadata:
  name: binary-loader
  namespace: k0s-fileserver
spec:
  restartPolicy: Never
  containers:
  - name: loader
    image: registry.local/k0rdent-enterprise/busybox:1.36.1  # Replace registry.local with your registry hostname
    command: ["sh", "-c", "sleep 3600"]
    volumeMounts:
    - name: files
      mountPath: /data
  volumes:
  - name: files
    persistentVolumeClaim:
      claimName: k0s-fileserver-data

Apply and wait for the pod to be ready:

kubectl apply -f k0s-binary-loader.yaml
kubectl wait --for=condition=Ready pod/binary-loader -n k0s-fileserver --timeout=60s

Note

Use the k0s binary downloaded in the Download k0s binaries step.

Copy the binary into the PVC:

kubectl exec -n k0s-fileserver binary-loader -- mkdir -p /data/k0rdent-enterprise
kubectl cp k0s-v1.35.1+k0s.1-amd64 k0s-fileserver/binary-loader:/data/k0rdent-enterprise/k0s-v1.35.1+k0s.1-amd64

Remove the loader pod when done:

kubectl delete pod binary-loader -n k0s-fileserver

Note

When upgrading k0s, add the new binary to the file server without removing the existing one. See Updating k0s binaries on the file server in the upgrade guide.

Deploying the file server#

A LoadBalancer Service receives a stable external IP address from your load balancer controller. The port is statically declared and not subject to Kubernetes ephemeral port allocation.

Save the following as k0s-fileserver.yaml:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: k0s-fileserver-config
  namespace: k0s-fileserver
data:
  default.conf: |
    server {
      listen 80;
      server_name _;

      root /var/www/html;
      sendfile        on;
      tcp_nopush      on;
      tcp_nodelay     on;
      keepalive_timeout 70;
      client_max_body_size 512m;
      proxy_max_temp_file_size 0;

      location / {
        autoindex on;
      }

      location /healthz {
        return 200 'OK';
        add_header Content-Type text/plain;
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: k0s-fileserver
  namespace: k0s-fileserver
  labels:
    app: k0s-fileserver
spec:
  replicas: 1
  selector:
    matchLabels:
      app: k0s-fileserver
  template:
    metadata:
      labels:
        app: k0s-fileserver
    spec:
      containers:
      - name: nginx
        image: registry.local/nginx:1.30.2  # Replace registry.local with your registry hostname; not in the airgap bundle
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 10
        volumeMounts:
        - name: config
          mountPath: /etc/nginx/conf.d
        - name: files
          mountPath: /var/www/html
      volumes:
      - name: config
        configMap:
          name: k0s-fileserver-config
      - name: files
        persistentVolumeClaim:
          claimName: k0s-fileserver-data
---
apiVersion: v1
kind: Service
metadata:
  name: k0s-fileserver
  namespace: k0s-fileserver
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: k0s-fileserver

Apply:

kubectl apply -f k0s-fileserver.yaml

Wait for the external IP to be assigned:

kubectl get service k0s-fileserver -n k0s-fileserver --watch
NAME              TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)   AGE
k0s-fileserver    LoadBalancer   10.96.12.34    192.168.1.100   80/TCP    90s

Note

EXTERNAL-IP will show <pending> until the load balancer controller assigns an address. On bare-metal clusters using MetalLB, this requires a configured IPAddressPool.

Verify the endpoint is reachable:

export EXTERNAL_IP="$(kubectl get svc k0s-fileserver -n k0s-fileserver -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"
export FILESERVER_URL="http://$EXTERNAL_IP/k0rdent-enterprise"
curl -I "$FILESERVER_URL/k0s-v1.35.1+k0s.1-amd64"
HTTP/1.1 200 OK
Server: nginx/1.30.2
Date: Tue, 17 Jun 2025 21:32:28 GMT
Content-Type: application/octet-stream
Content-Length: 268006461
Last-Modified: Tue, 17 Jun 2025 21:26:59 GMT
Connection: keep-alive
ETag: "6851dda3-ff9743d"
Accept-Ranges: bytes

Note

Recommended for long-term stability, create a DNS record pointing a hostname (e.g. fileserver.k0rdent.local) to the load balancer address and use that hostname in controller.globalK0sURL. Load balancer IPs can change over the lifetime of the cluster — after a MetalLB reconfiguration or infrastructure reprovisioning. With a DNS record, only the DNS entry needs updating; the Mirantis k0rdent Enterprise configuration and existing ClusterDeployment objects remain unchanged.

Note

The file server can also be exposed over HTTPS (using a LoadBalancer Service with TLS termination) or via an Ingress resource, depending on what is available in your environment. These options require additional configuration specific to your infrastructure — such as certificate management, ingress controller setup, and DNS — and are not covered in this guide.

Using the download URL in a ClusterTemplate#

controller.globalK0sURL tells Mirantis k0rdent Enterprise where to download the k0s binary for child cluster nodes. It is set as part of the airgapped installation — see Airgapped installation for the full configuration reference. Instead of pointing to the public Mirantis download server, set it to your file server base URL:

--set controller.globalK0sURL=http://<FILESERVER-ADDRESS>/k0rdent-enterprise

The value can also be updated after installation by editing the Management object under .spec.core.kcm.config.

For HTTPS with a custom CA certificate, also set controller.k0sURLCertSecret to the name of a Secret containing ca.crt. See Configuring a global k0s URL for details.

Warning

controller.globalK0sURL is a global setting — it applies to all child clusters deployed from this management cluster. If the file server endpoint changes, you must update this value and re-provision affected clusters. Treat the file server endpoint with the same stability guarantees as a container registry address.