N.E.V.E.R. (Network Endpoint Validation with Endless Retries) is a lightweight Go application that obsessively checks whether a
TCP,HTTP, orICMPtarget is reachable. It loops endlessly until the target responds — or until it’s killed.
Designed to run as a Kubernetes initContainer, N.E.V.E.R. ensures your service dependencies are fully up before anything else gets a chance to boot.
- Continuously retries until the target responds — no backoff, no shame.
- Supports multiple concurrent targets, each with its own config.
- Configurable entirely via command-line arguments.
- Exits with
0the moment everything is ready.
Whether you're waiting on a port, ping, or a 200 OK, N.E.V.E.R. backs down never.
never accepts the following command-line flags:
| Flag | Type | Default | Description |
|---|---|---|---|
--default-interval |
duration | 2s |
Default interval between checks. Can be overridden for each target. |
--version |
bool | false |
Show version and exit. |
--help, -h |
bool | false |
Show help. |
never accepts "dynamic" flags that can be defined in the startup arguments.
Use the --<TYPE>.<IDENTIFIER>.<PROPERTY>=<VALUE> format to define targets.
Types are: http, icmp or tcp.
-
--http.<IDENTIFIER>.name=stringThe name of the target. If not specified, it uses the<IDENTIFIER>as the name. -
--http.<IDENTIFIER>.address=stringThe target's address. Resolvable: See Resolving Variables below.--http.<IDENTIFIER>.interval=durationThe interval between HTTP requests (e.g.,1s). Overwrites the global--default-interval.
-
--http.<IDENTIFIER>.method=stringThe HTTP method to use (e.g.,GET,POST). Defaults toGET. -
--http.<IDENTIFIER>.header=stringA HTTP header inkey=valueformat. Can be specified multiple times. Example:Authorization=Bearer tokenResolvable: See Resolving Variables below. -
--http.<IDENTIFIER>.allow-duplicate-headers=boolAllow duplicate headers. Defaults tofalse. -
--http.<IDENTIFIER>.expected-status-codes=stringA comma-separated list of expected HTTP status codes or ranges (e.g.,200,301-302). Defaults to200. -
--http.<IDENTIFIER>.skip-tls-verify=boolWhether to skip TLS verification. Defaults tofalse. -
--http.<IDENTIFIER>.timeout=durationThe timeout for the HTTP request (e.g.,5s). Defaults to1s.
-
--icmp.<IDENTIFIER>.name=stringThe name of the target. If not specified, it uses the<IDENTIFIER>as the name. -
--icmp.<IDENTIFIER>.address=stringThe target's address. Resolvable: See Resolving Variables below. -
--icmp.<IDENTIFIER>.interval=durationThe interval between ICMP requests (e.g.,1s). Overwrites the global--default-interval. -
--icmp.<IDENTIFIER>.read-timeout=durationThe read timeout for the ICMP connection (e.g.,1s). Defaults to1s. -
--icmp.<IDENTIFIER>.write-timeout=durationThe write timeout for the ICMP connection (e.g.,1s).Defaults to1s.
-
--tcp.<IDENTIFIER>.name=stringThe name of the target. If not specified, it uses the<IDENTIFIER>as the name. -
--tcp.<IDENTIFIER>.address=stringThe target's address. Resolvable: See Resolving Variables below. -
--tcp.<IDENTIFIER>.interval=durationThe interval between ICMP requests (e.g.,1s). Overwrites the global--default-interval.
Each address field can be resolved using environment variables, files, JSON, YAML, and INI files.
env: – Resolves environment variables. Example:env:PATHreturns the value of thePATHenvironment variable.file: – Resolves values from a simple key-value file. Example:file:/config/app.txt//KeyNamereturns the value associated withKeyNameinapp.txt.json: – Resolves values from a JSON file. Supports dot notation for nested keys. Example:json:/config/app.json//database.hostreturnshostfield underdatabaseinapp.json. It is also possible to indexing into arrays (e.g.,json:/config/app.json//servers.0.host).yaml: – Resolves values from a YAML file. Supports dot notation for nested keys. Example:yaml:/config/app.yaml//server.portreturnsportunderserverinapp.yaml.It is also possible to indexing into arrays (e.g.,yaml:/config/app.yaml//servers.0.host).ini: – Resolves values from an INI file. Can specify a section and key, or just a key in the default section. Example:ini:/config/app.ini//Section.Keyreturns the value ofKeyunderSection.- No prefix – Returns the value as-is, unchanged.
HTTP headers values can also be resolved using the same mechanism, (from a environment variable --http.<IDENTIFIER>.header="header=env:SECRET_HEADER" or from a file --http.<IDENTIFIER>.header="header=file:PATH_TO_FILE").
never \
--http.web.address=http://example.com:80 \
--http.web.method=GET \
--http.web.expected-status-codes=200,204 \
--http.web.header="Authorization=Bearer token" \
--http.web.header="Content-Type=application/json" \
--http.web.skip-tls-verify=false \
--default-interval=5snever \
--http.web.address=http://example.com:80 \
--tcp.db.address=tcp://localhost:5432 \
--default-interval=10sProxy Settings: Proxy configurations (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) are managed via environment variables.
Click here to see the flowchart
graph TD;
classDef noFill fill:none;
classDef violet stroke:#9775fa;
classDef green stroke:#2f9e44;
classDef error stroke:#fa5252;
classDef transparent stroke:none,font-size:20px;
subgraph MainFlow[ ]
direction TB
start((Start)) --> attemptConnect[Attempt to connect to <font color=orange>TARGET_ADDRESS</font>];
class start violet;
subgraph RetryLoop[Retry Loop]
subgraph InnerLoop[ ]
direction TB
attemptConnect -->|Connection successful| targetReady[Target is ready];
attemptConnect -->|Connection failed| waitRetry[Wait for retry <font color=orange>CHECK_INTERVAL</font>];
waitRetry --> attemptConnect;
end
end
targetReady --> processEnd((End));
class processEnd violet;
waitRetry --> processEnd;
end
programTerminated[Program terminated or canceled] --> processEnd;
class programTerminated error;
class start,attemptConnect,targetReady,waitRetry,processEnd,programTerminated,MainFlow,RetryLoop noFill;
class MainFlow,RetryLoop transparent;
Only when using ICMP checks in Kubernetes, it's important to ensure that the container has the necessary permissions to send ICMP packets. It is necessary to add the CAP_NET_RAW capability to the container's security context.
Example:
- name: wait-for-host
image: ghcr.io/containeroo/never:latest
env:
- name: TARGET_ADDRESS
value: icmp://hostname.domain.com
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
add: ["CAP_NET_RAW"]For TCP and HTTP checks, the container does not require any additional permissions.
Click here to see the flowchart
flowchart TD;
direction TB
classDef noFill fill:none;
classDef violet stroke:#9775fa;
classDef green stroke:#2f9e44;
classDef error stroke:#fa5252;
classDef decision stroke:#1971c2;
classDef transparent stroke:none,font-size:20px;
subgraph MainFlow[ ]
direction TB
processStart((Start)) --> createRequest[Create HTTP request for <font color=orange>TARGET_ADDRESS</font>];
class start processStart;
createRequest --> addHeaders[Add headers from <font color=orange>HTTP_HEADERS</font>];
addHeaders --> addSkipTLS[Add skip TLS verify if <font color=orange>HTTP_SKIP_TLS_VERIFY</font> is set];
addSkipTLS --> sendRequest[Send HTTP request];
subgraph RetryLoop[Retry Loop]
subgraph InnerLoop[ ]
direction TB
sendRequest --> checkTimeout{Answers within <font color=orange>DIAL_TIMEOUT</font>?};
class checkTimeout decision;
checkTimeout -->|Yes| checkStatusCode[Check response status code <font color=orange>HTTP_EXPECTED_STATUS_CODES</font>];
checkStatusCode --> statusMatch{Matches?};
class statusMatch decision;
statusMatch -->|Yes| targetReady[Target is ready];
class targetReady success;
statusMatch -->|No| targetNotReady[Target is not ready];
class targetNotReady error;
targetNotReady --> waitRetry[Wait for retry <font color=orange>CHECK_INTERVAL</font>];
waitRetry --> sendRequest;
end
end
targetReady --> processEnd((End));
class processEnd violet;
end
programTerminated[Program terminated or canceled] --> processEnd;
class programTerminated error;
class processStart,createRequest,addHeaders,addSkipTLS,sendRequest,checkTimeout,checkStatusCode,statusMatch,targetReady,targetNotReady,waitRetry,programTerminated,processEnd,MainFlow,RetryLoop noFill;
class MainFlow,RetryLoop transparent;
Click here to see the flowchart
flowchart TD;
direction TB
classDef noFill fill:none;
classDef violet stroke:#9775fa;
classDef green stroke:#2f9e44;
classDef error stroke:#fa5252;
classDef decision stroke:#1971c2;
classDef transparent stroke:none,font-size:20px;
subgraph MainFlow[ ]
direction TB
processStart((Start)) --> createRequest[Create ICMP request for <font color=orange>TARGET_ADDRESS</font>];
class start processStart;
createRequest --> sendRequest[Send ICMP request];
subgraph RetryLoop[Retry Loop]
subgraph InnerLoop[ ]
direction TB
sendRequest --> checkTimeout{Answers within timeouts <font color=orange>DIAL_TIMEOUT</font>/<font color=orange>ICMP_READ_TIMEOUT</font>?};
checkTimeout -->|Connection successful| targetReady[Target is ready];
checkTimeout -->|Connection failed| waitRetry[Wait for retry <font color=orange>CHECK_INTERVAL</font>];
waitRetry --> sendRequest;
end
end
targetReady --> processEnd((End));
class processEnd violet;
end
programTerminated[Program terminated or canceled] --> processEnd;
class programTerminated error;
class processStart,createRequest,sendRequest,checkTimeout,targetReady,waitRetry,processEnd,MainFlow,RetryLoop noFill;
class MainFlow,RetryLoop transparent;
Configure your Kubernetes deployment to use this init container:
initContainers:
- name: wait-for-vm
image: ghcr.io/containeroo/never:latest
args:
- --icmp.vm.address=hostname.domain.tld
securityContext: # icmp requires CAP_NET_RAW
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
add: ["CAP_NET_RAW"]
- name: wait-for-it
image: ghcr.io/containeroo/never:latest
args:
- --target.postgres.address=postgres.default.svc.cluster.local:9000/healthz # use healthz endpoint to check if postgres is ready
- --target.postgres.method=POST
- --target.postgres.header=Authorization=env:BEARER_TOKEN
- --target.postgres.expected-status-codes=200,202
- --target.redis.name=redis
- --target.redis.address=redis.default.svc.cluster.local:6437
- --tcp.vaultkey.address=valkey.default.svc.cluster.local:6379
- --tcp.vaultkey.interval=5s
- --tcp.vaultkey.timeout=5s
envFrom:
- secretRef:
name: bearer-tokenThis project is licensed under the Apache License. See the LICENSE file for details.