Skip to content

feat: add dms-healthcheck script for container health checks - #4706

Merged
casperklein merged 13 commits into
docker-mailserver:masterfrom
radicand:feat/dms-healthcheck
Jun 10, 2026
Merged

feat: add dms-healthcheck script for container health checks#4706
casperklein merged 13 commits into
docker-mailserver:masterfrom
radicand:feat/dms-healthcheck

Conversation

@radicand

Copy link
Copy Markdown
Member

Summary

Adds a new dms-healthcheck script to target/bin/ that performs an intelligent health check by querying supervisorctl only for services that are actually enabled in the current DMS installation.

The script sources /etc/dms-settings (written during container startup) to determine which optional features are enabled, then checks that all corresponding supervisor-managed services are in the RUNNING state. supervisorctl status returns exit code 0 only when all named services are running — any non-running service results in a non-zero exit, which container runtimes (Docker, Kubernetes) correctly interpret as unhealthy.

If /etc/dms-settings does not yet exist, the container is still initializing and the check exits non-zero immediately to avoid false positives during startup.

Covered Services

Service Condition
cron, rsyslog, postfix, changedetector Always
dovecot SMTP_ONLY != 1
opendkim ENABLE_OPENDKIM=1
opendmarc ENABLE_OPENDMARC=1
amavis ENABLE_AMAVIS=1
clamav ENABLE_CLAMAV=1
fail2ban ENABLE_FAIL2BAN=1
postgrey ENABLE_POSTGREY=1
postsrsd ENABLE_SRS=1
rspamd-redis ENABLE_RSPAMD_REDIS=1
rspamd ENABLE_RSPAMD=1
getmail ENABLE_GETMAIL=1
mta-sts-daemon ENABLE_MTA_STS=1
saslauthd_ldap / saslauthd_rimap ENABLE_SASLAUTHD=1
fetchmail / fetchmail-N ENABLE_FETCHMAIL=1 (parallel-aware)

Changes

  • target/bin/dms-healthcheck — new script (installed to /usr/local/bin/ via existing COPY target/bin/* in Dockerfile)
  • Dockerfile — adds HEALTHCHECK directive using the new script
  • compose.yaml — updates healthcheck from the basic SMTP port check to use dms-healthcheck

Motivation

Discussed in docker-mailserver-helm #135 by @polarathene — the Helm chart currently embeds probe logic that needs to know which services are enabled, but this belongs upstream in DMS itself. With this script, external orchestrators can simply call dms-healthcheck as their probe command without needing to replicate any of this logic.

@polarathene polarathene left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking the time to put this PR together! ❤️

I've provided some revisions that will improve the correctness of the check, but there's a related issue that'll need to be resolved separately and won't be identified by this healthcheck script into fixed.


Undetected failure - Infinite service restarts due to startsecs=0

UPDATE: Raised as a separate issue.

I'd like to also point out that presently services that immediately fail will result in log spam with repeated crashes (this could happen with fetchmail parallel too but that cause was fixed for v16 of DMS).

According to the supervisord docs, these scenarios should stop restarting the services (default retry attempts is startretries = 3), but due to all our services setting the default startsecs = 1 to 0, such failure behaviour is skipped and won't be detected by supervisorctl status as a result (like shown in the issue linked, RUNNING state with exit status 0 is returned).

This startsecs=0 bug has been around since the introduction of the supervisord into DMS (Aug 2017). A separate PR should tackle that concern as increasing this back to 1 will slow down container startup by 1 sec per service since we sequentially invoke supervisorctl start ... manually for each service individually.

When this concern is resolved, the service goes to BACKOFF state during retries (progressively taking longer before each retry), followed by entering a FATAL state if the failing service is unable to achieve an uptime of startsecs before the startretries count is exhausted. Which looks like this from supervisorctl status fetchmail-2 output:

fetchmail-2                      FATAL     Exited too quickly (process log may have details)

Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck Outdated
Comment thread compose.yaml Outdated
Comment thread target/bin/dms-healthcheck Outdated
Comment thread Dockerfile Outdated
Comment on lines +299 to +300
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD dms-healthcheck

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD dms-healthcheck
# NOTE: This is not part of the OCI image spec (limited compatibility at runtime)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD dms-healthcheck

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improved per suggestion

@casperklein casperklein Jun 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line break seems unnecessary to me.


Do we really want/need 3 retries?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@casperklein

The line break seems unnecessary to me.

Personally I prefer the distinction/separation if there's all those HEALTHCHECK options defined, and that's similar to what is shown in the HEALTHCHECK docs example.

Given feedback with suggestion below, I do agree that it's not worthwhile and we can collapse it back to a single line 👍


Do we really want/need 3 retries?

The default retries is already 3, as is the interval of 30s.

If anything I'd raise more concern with --interval=30s as that is how long we're delaying the first health check which affects any containers using depends_on with the health status of DMS.

Retries being more than 1 is absolutely valid, if the health check is successful at the first attempt, the container is marked healthy, but if it failed, you may still want to permit checking again after a moment in the event the healthcheck was run when a service was restarted (and recovered), not immediately assume failure.

The --timeout shouldn't need to be lowered (default 30s), on the basis that we don't expect any part of the script to hang, that SIGTERM should be sent any earlier. I'd rather introduce a lower timeout as a response to a real report where investigation is unable to resolve what causes the hang to occur.

DMS is up and ready by default in less than 5s on my system, a bit longer on a budget VPS instance, and some other services when enabled (like Rspamd IIRC which is intended to become default in future) can slow that down (Amavis adds some delay too IIRC).

If we want to assume that DMS should be up within 10-15s we could use an interval of 5s, or with other services enabled, give some additional slack. As this is the Dockerfile healthcheck (also applies for Docker Compose), we have additional options to configure (defaults: --start-period=0s + --start-interval=5s) so that the polling interval is shorter only during the initial container startup (duration window defined by --start-period). Retry failures do not count during the start period, but that duration is cut short as soon as as a healthcheck is successful (such that if a failure then occurs and subsequent healthchecks fail, even if they were within the declared start period window, they'd no longer be ignored).

The 5s --start-interval default is sufficient for DMS, so just raise --start-period

Suggested change
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD dms-healthcheck
HEALTHCHECK --start-period=30s CMD dms-healthcheck

@casperklein casperklein Jun 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't imagine a case, where the health check only succeed on the 3rd attempt. That's why I questioned the retry amount. We can leave it as it is..

--start-period=30 seems like a good addition 👍

Edit:

HEALTHCHECK --interval=30s --timeout=5s --retries=3

This gives the container 1:30min to start, before becoming unhealthy.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't imagine a case, where the health check only succeed on the 3rd attempt.

First time starting the container and setting up a user? For someone new to it, that can take more than 30s depending how they go about it.

For me it was when I was setting up automated test examples with multiple containers, setting a lower interval that is too aggressive might fail the first try depending on deployment environment. Thankfully I came across --start-period which resolved that concern completely.


HEALTHCHECK --interval=30s --timeout=5s --retries=3

This gives the container 1:30min to start, before becoming unhealthy.

Technically during a container startup the status would be starting (which we can still consider as unhealthy, at least a depends_on would), and if no check passes, then we stay in that unhealthy state until officially switching to an unhealthy status at 90sec uptime, yes.

We resolved that concern for startup healthchecks via a start period and shorter start interval (which is immune to --retries within the start period) 👍

Thus the starting status can switch to a healthy status much sooner.

  • While a unhealthy status still takes 90s (30s start period + 30s x2 interval checks. Exhausting the start period seems to count as 1 failure, we then wait --interval until the next check is performed which fails and so on).
  • If you need to assume failure earlier, you can override these healthcheck params, or have your own logic in place when healthy has not been reported after X time.

For actual runtime, the first healthcheck could fail for various reasons (a single check on a port being bound, not so much, but this PR covers a variety of services), such as:

  • A service may not report a RUNNING status if it just crashed for example but it could recover shortly after.
  • Intermittent connectivity (perhaps using Fetchmail/Getmail and that fails to connect to an external mail server? It'll try again and the connection may succeed).

30s or 90s before your monitoring tools can signal the unhealthy status isn't that much of a difference for your ability to respond, but it does help ensure we avoid false positives wasting your time to respond. Retries are important, and given the types of failures, it's also important that the retries are given enough padding.

Container startup time is a totally different scenario and generally more predictable (but as mentioned depending on context, there can be some where this will take longer).

Adds a new `dms-healthcheck` script that performs an intelligent
health check by querying supervisorctl for only the services that
are actually enabled in the current installation.

The script sources `/etc/dms-settings` (written during startup) to
determine which optional features are enabled, then verifies all
corresponding supervisor-managed services are in the RUNNING state.

Key behaviors:
- Returns only exit code 0 (healthy) or 1 (unhealthy) per Docker
  HEALTHCHECK spec
- Detects STARTING/BACKOFF states as unhealthy (not just non-RUNNING)
- Supports parallel service instances (fetchmail, getmail) via helper
- No stdout output to keep Docker health log buffer clean

The Dockerfile HEALTHCHECK directive is added with an OCI compat note.
The compose.yaml retains its existing ss-based health check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@radicand
radicand force-pushed the feat/dms-healthcheck branch from f05daa5 to 5440ce4 Compare May 24, 2026 14:01
@radicand

radicand commented May 24, 2026

Copy link
Copy Markdown
Member Author

[Agent comment] I've updated the PR with all your feedback incorporated:

Changes Made

Exit code semantics — Now uses test 'RUNNING' = "$(supervisorctl status "${SERVICES[@]}" | awk '{print $2}' | sort -u)" to ensure only exit codes 0 (healthy) or 1 (unhealthy) per Docker HEALTHCHECK spec. This also correctly detects STARTING/BACKOFF/FATAL states as unhealthy.

Suppressed stdout — No output during healthy checks. Docker's health log buffer stays clean.

Saslauthd alignment — Folded to one-liner with proper formatting: [[ ${ENABLE_SASLAUTHD} -eq 1 ]] && SERVICES+=("saslauthd_${SASLAUTHD_MECHANISMS}")

Parallel service helper — Extracted _get_services_for() function exactly as suggested. Queries supervisorctl status output for SERVICE-N patterns instead of walking the filesystem.

Future-proof for GETMAIL_PARALLEL — Already included support for getmail parallel instances (PR #4675). Will work immediately when that lands.

ENABLE_MTA_STS fallback removed — Rebased on latest master which includes PR #4707. Now directly reads ENABLE_MTA_STS from /etc/dms-settings.

OCI compatibility note — Added comment to HEALTHCHECK in Dockerfile.

compose.yaml kept as-is — Reverted to keep the original ss-based SMTP port check. Acknowledges that checking the port is more advanced than supervisor status alone.

Tested

I tested this locally on my own running instance (in k8s).

@polarathene

polarathene commented May 24, 2026

Copy link
Copy Markdown
Member

Please disclose the extent to your use of AI. I assume it's Claude since one interaction only really stood out as highly unlikely for a human 🤔 (other signals I can give the benefit of the doubt)

You use Copilot, Claude Opus is a supported model.


Attribution was retained in PR commit, confirming copilot use:

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Please clarify how much copilot was involved in the PR description and subsequent comments, how much interaction am I having with an actual human during review?

@polarathene polarathene left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an entry to the changelog.


Please resolve the lint failure without having your AI agent over-engineer the solution.

It may tend to prefer applying best practices if it is unable to determine relevant context to why it could avoid adding complexity. I'd prefer a simple script that is easy to grok and maintain by humans that can grasp why this logic is safe.

If I didn't get the impression I was engaging with AI for review, I'd provide a clearer suggestion here that could be applied. AI disclosure should have been provided earlier in your PR description tbh, but I see we haven't required that in the PR template.


compose.yaml kept as-is — Reverted to keep the original ss-based SMTP port check. Acknowledges that checking the port is more advanced than supervisor status alone.

It is not, it would be complimentary.

  • It would be better to incorporate the listening state of the port into the healthcheck script was my input but only if reports were received where it made a meaningful difference so that we'd have context of a false positive (Postfix binds to port 25 quite quickly once started that for it to be deemed RUNNING by supervisord but not have binded port 25, I'd like to know the cause of failure).
  • My advice was to leave the healthcheck example in compose.yaml, but make users aware of the built-in dms-healthcheck command.

Suppressed stdout — No output during healthy checks. Docker's health log buffer stays clean.

Meaningful logs were still relevant, such as the one to communicate the cause of an unhealthy state (during container startup when /etc/dms-settings doesn't exist yet).

EDIT: As per my review feedback on that early exit from the script, it's less likely to be meaningful (/etc/dms-settings is created quite early in startup, or if the container restarted it'd already be present and potentially outdated). So no need to revert.

Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck Outdated
@radicand

Copy link
Copy Markdown
Member Author

Please add an entry to the changelog.

Please resolve the lint failure without having your AI agent over-engineer the solution.

@polarathene thanks for taking the time to review. I admit I was in a bit of a rush this morning and it was a case of "looks good to me, runs and functions". The feedback is appropriate. I'll work out the rest and follow up again.

@radicand

Copy link
Copy Markdown
Member Author

Please disclose the extent to your use of AI. I assume it's Claude since one interaction only really stood out as highly unlikely for a human 🤔 (other signals I can give the benefit of the doubt)

You use Copilot, Claude Opus is a supported model.

Attribution was retained in PR commit, confirming copilot use:

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Please clarify how much copilot was involved in the PR description and subsequent comments, how much interaction am I having with an actual human during review?

Hey there - I use AI in drafting, and yes, with Claude generally. I don't blind commit. You're interacting with me here in this PR - I do want to close my PR from the sister helm repo and this HC script is not incredibly complicated as a concept. Hope that helps clarify.

Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
@polarathene

polarathene commented May 25, 2026

Copy link
Copy Markdown
Member

Hope that helps clarify.

Thanks! All good, I just needed to verify if AI was being used and to what extent. I don't mean any disrespect by it 😅


I admit I was in a bit of a rush this morning and it was a case of "looks good to me, runs and functions".

No worries I understand, it happens.

I use AI in drafting, and yes, with Claude generally.

I don't blind commit. You're interacting with me here in this PR

I'm not against using AI, but without upfront disclosure it can sometimes be difficult to discern how much of the interactions (PR description, review comments, update comment) are delegated to tooling.

The PR description and notably the update comment had signals of AI use. The review comments were terse but sometimes seemed unnecessary to individually reply to like:

It was becoming unclear if I was interacting with AI only (has happened in reviews before). When decisions were made / committed that didn't seem well thought out, that's when I needed to sanity check how AI was involved.


this HC script is not incredibly complicated as a concept.

While that is true, there were bugs identified along with questionable commentary. If the use of AI is at fault for that, it eats away into my time as a reviewer which I'm not fond of.

@polarathene polarathene added kind/new feature A new feature is requested in this issue or implemeted with this PR area/scripts area/features labels May 25, 2026
@polarathene polarathene added this to the v16.0.0 milestone May 25, 2026
radicand and others added 6 commits May 24, 2026 20:03
Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
… is for

Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
…omments

Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
…ript

fix: revert incorrectly reverted healthcheck alignment in compose.yaml (and add comment)
docs: add comment in Dockerfile about HEALTHCHECK support
docs: add future note/todo about looking for listening ports as well, if the future needs it.
@radicand

Copy link
Copy Markdown
Member Author

@polarathene I think I've got the bulk of the feedback addressed at this point; I need to resolve the lint still and will ping you when that's done.

@polarathene

Copy link
Copy Markdown
Member

I need to resolve the lint still

You may just annotate them to ignore the lint if you like.

My earlier feedback was mapfile/read suggested from the lint are not necessary. The script is straight-forward, the service names don't contain white-space, splitting is intentional and safe. Furthermore the grep regex filter ensures that nothing else should get through unintentionally.

If another maintainer chimes in later to disagree, then we'll address that, but my opinion is there is no need to go to extra lengths.

input path is controlled and behavior is intentional

@polarathene polarathene left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the compose.yaml, you should be able to switch to the "Files changed" tab of this PR and click "Add to batch" on each suggestion to apply them all via a single commit.

The compose.yaml suggestion looks borked for what it'd modify, you'd need to manually apply that one (and may want to wait until @casperklein provides feedback).

Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck Outdated
Comment thread target/bin/dms-healthcheck
Comment thread CHANGELOG.md Outdated
Comment thread compose.yaml Outdated
radicand and others added 2 commits May 24, 2026 21:37
Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
@radicand

Copy link
Copy Markdown
Member Author

Apart from the compose.yaml, you should be able to switch to the "Files changed" tab of this PR and click "Add to batch" on each suggestion to apply them all via a single commit.

The compose.yaml suggestion looks borked for what it'd modify, you'd need to manually apply that one (and may want to wait until @casperklein provides feedback).

Cheers, I'd missed that feature -- appreciate you calling it out. I'm good with this now if/when @casperklein weighs in. Really appreciate you taking the time and being thorough here, and my apologies for my sloppiness earlier today.

polarathene
polarathene previously approved these changes May 25, 2026

@polarathene polarathene left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 Thanks!

# Ensure all enabled services report a 'RUNNING' status.
# - We cannot rely upon the exit status of `supervisorctl status` (due to `STARTING`/`BACKOFF`).
# - Returns an exit status of `0` only when the 2nd column reduces to `RUNNING`.
test 'RUNNING' = "$(supervisorctl status "${SERVICES[@]}" | awk '{print $2}' | sort -u)"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test 'RUNNING' = "$(supervisorctl status "${SERVICES[@]}" | awk '{print $2}' | sort -u)"
if ! [[ "RUNNING" == "$(supervisorctl status "${SERVICES[@]}" | awk '{print $2}' | sort -u)" ]]; then
exit 1
fi
# Check if postfix is listening on port tcp/25
if ! ss -l -4 -n | grep -qF '0.0.0.0:25'; then
exit 1
fi
# DMS is healthy
exit 0

To have all health checks in one place, we could also add the port check here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The port 25 check was left in a comment as a TODO, should we actually find a need/benefit in having that check. Presently AFAIK it's just an assumption, and it'd be nicer to know a scenario where DMS has Postfix reporting RUNNING but isn't listening on port 25 when it's expected to.

In that same vein we could also query other ports on other services, but even then a bound port for a service to listen on doesn't equate to a working service port 😅

If you do insist on including it, I won't challenge that but we could express the conditions in a shell group { ... } with a single || exit 1 instead? (to ensure any non-zero status is coalesced into 1):

Suggested change
test 'RUNNING' = "$(supervisorctl status "${SERVICES[@]}" | awk '{print $2}' | sort -u)"
{
# Verify all enabled services are ready:
test 'RUNNING' == "$(supervisorctl status "${SERVICES[@]}" | awk '{print $2}' | sort -u)" && \
# Verify port 25 (Postfix) is bound:
( ss --listening --ipv4 --tcp | grep --silent ':smtp' )
} || exit 1

For me at least, stacking a bunch of if statements and inverting the condition to trigger exit 1 in each seemed like a code smell 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO: A service check (e.g. sending a mail) > port check > supervisor status

Replacing the port check with supervisor status seems like a little regression to me.

In that same vein we could also query other ports on other services

Absolutely. But my take on this PR was to improve things a bit and not to make them 100% bullet proof and give OP additional homework 😉

For me at least, stacking a bunch of if statements and inverting the condition to trigger exit 1 in each seemed like a code smell 🤔

I agree that we disagree 😆
Compared to multiple conditions spread across multiple lines, that are glued together with && \ and using an additional sub-shell, I find my proposal in that case much more readable and explicit.


That said, I am fine with the current state of this PR, weather the port check is included or not.
And if it's included, I am fine with both variants 👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO: A service check (e.g. sending a mail) > port check > supervisor status

Replacing the port check with supervisor status seems like a little regression to me.

I think that depends on perspective?

For example your checking port 25 as your healthcheck, but sending a mail from DMS would use an outbound port 25, which has nothing to do with that, whilst the listening port for mail submission is also 587 or 465.

You may also just want to use DMS with Fetchmail/Getmail and Dovecot for a test, where mail can be retrieved from a remote mail server, and then delivered via LMTP, or other means that do not depend on Postfix listening to port 25 (or Postfix at all for that matter). A scenario like has different expectations for what a healthy/ready DMS container means.

If Postfix is healthy, there's a good chance it's already listening on port 25. I've asked for what scenarios that is not the case, but haven't yet had a clear example cited?

NOTE: Presently, you would be correct however that supervisor status here is a regression. Until a follow-up PR addresses the startsecs=0 concern I raised with Supervisord (services instantly are treated as RUNNING state due to this, even when they're in a failure loop, it also prevents a service being marked as unhealthy as retry limits only apply to failure transitions from states that are not RUNNING which resets the retry count).


In that same vein we could also query other ports on other services

Absolutely. But my take on this PR was to improve things a bit and not to make them 100% bullet proof

I think there's a misunderstanding here. I was trying to highlight that it'd be adding complexity for little to no gain for our project and users.

I would rather we add port checks only when we have evidence of scenarios that cause that, so that we don't just carry redundant checks for the sake of it.

If we carry the port checks, extra work should be done for communicating that kind of failure, rather than just reporting an unhealthy status, as that may not lead to maintainers being made aware of a problem causing it, especially if the fix is just to have a restart policy when the container is unhealthy?

If instead it were marked healthy but had a problem with a port, it's far more likely a user would report this to us, or PR a port check into the healthcheck script, which comes with valuable context that it's a known issue, and presumably one we've not been able to troubleshoot/resolve the cause of it.


and give OP additional homework 😉

I don't think that is necessary 🤨


For me at least, stacking a bunch of if statements and inverting the condition to trigger exit 1 in each seemed like a code smell 🤔

I agree that we disagree 😆
Compared to multiple conditions spread across multiple lines, that are glued together with && \ and using an additional sub-shell, I find my proposal in that case much more readable and explicit.

That's fair, but the proposal I gave still resulted in the same conditions that you'd have wrapped with if [[ ... ]];then + exit 1 + fi. If that's all that is done, it's just extra noise to communicate "N conditions (any true) => exit 1", which can really add up as more conditions are accumulated.

You're still going to see those same condition regardless if they're wrapped in several lines of syntax noise, or if each check is co-located right next to each other and scoped to individual lines (which the subshell was intended to help convey, pragmatically speaking in this context there's no notably overhead in doing so?).

You know me, I tend to prefer verbosity (long options, detailed commentary inline docs for context, etc), but in this case my mental model (monotropic / gestalt thinking) is biased towards expressing the logic with a clear encapsulated pattern 😅 (which I appreciate as an explicit flow to grok for readability)

That said, abstractions can add complexity, so as the author perhaps I'm blinded by bias 😓 (I did spend some time trying to see it your way too... but if the conditions themselves were replaced with _all_enabled_services_are_running && _is_port_25_listening, I don't see an advantage with not being DRY here?)


That said, I am fine with the current state of this PR, weather the port check is included or not.
And if it's included, I am fine with both variants 👍

Sweet!

I'd prefer to avoid bike shedding and just defer implementation to the existing TODO comment which requests that we first have proper justification for the port 25 health check (or any other service ports).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Postfix is healthy, there's a good chance it's already listening on port 25.
I've asked for what scenarios that is not the case, but haven't yet had a clear example cited?

I can't imagine a scenario too. This was just to be safe in the future, when something unexpected happens.

Comment thread Dockerfile Outdated
Comment on lines +299 to +300
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD dms-healthcheck

@casperklein casperklein Jun 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line break seems unnecessary to me.


Do we really want/need 3 retries?

@polarathene

polarathene commented Jun 4, 2026

Copy link
Copy Markdown
Member

Posting this comment for my benefit of tracking (since some feedback is not being displayed linearly for me at least, it's a bit awkward to follow/track discussion updates):

@polarathene

polarathene commented Jun 9, 2026

Copy link
Copy Markdown
Member

@radicand I don't have permission to apply edits to your branch myself, so could you please do the following:

HEALTHCHECK --start-period=30s CMD dms-healthcheck
# TODO: The script could also verify that service ports have listeners bound (if justified).
# Implementation approach was discussed with two examples for checking port 25 here:
# https://github.com/docker-mailserver/docker-mailserver/pull/4706#discussion_r3344258001

These two discussions were referenced by my previous comment. After these changes are applied, PR is good for merging 👍

@radicand

Copy link
Copy Markdown
Member Author

@radicand I don't have permission to apply edits to your branch myself, so could you please do the following:

HEALTHCHECK --start-period=30s CMD dms-healthcheck
# TODO: The script could also verify that service ports have listeners bound (if justified).
# Implementation approach was discussed with two examples for checking port 25 here:
# https://github.com/docker-mailserver/docker-mailserver/pull/4706#discussion_r3344258001

These two discussions were referenced by my previous comment. After these changes are applied, PR is good for merging 👍

Sounds good - I've made the edits in 5ebd34b . Thanks again both for the thoughtful review.

@casperklein
casperklein merged commit b53c65a into docker-mailserver:master Jun 10, 2026
7 checks passed
bocan pushed a commit to bocan/docker-mailserver that referenced this pull request Jun 18, 2026
…r-mailserver#4706)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/features area/scripts kind/new feature A new feature is requested in this issue or implemeted with this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants