Repository navigation
Releases: amslertec/containly
Releases · amslertec/containly
Release list
Containly 2.7.4
Fixed
- Backups failed with "No such image: alpine:latest" when the helper image was removed from the
host. Containly checked its backup helper image only once per process: after an image prune or
a host cleanup removed it later, the image was never pulled again and every backup (scheduled
and manual) failed with(HTTP code 404) no such container - No such image: alpine:latestuntil
Containly was restarted. The helper image is now checked before every use and pulled again when
it is missing; if it disappears between the check and the start of the helper, Containly pulls
it once more and retries. Concurrent backups still share one pull. - Helper image pinned. Backup, restore, folder browser and volume file helpers no longer use
the movingalpine:latesttag but a pinned Alpine 3.22.2 image by its multi-arch digest (the
same image the NFS target already used), so every host runs the same known tools. The helpers
keep theirch.amslertec.containly.helperlabel. The Docker CLI helper is re-checked and
re-pulled the same way.
Changed
- Empty states centred. Empty states (for example Updates > "All up to date", empty lists,
no search hits, empty backup sections, settings tabs) now sit centred as one block, icon, title,
text and button, horizontally and vertically in the free content area instead of top-left, the
same on every page, on desktop and phone, in light and dark mode.
Update: docker compose pull && docker compose up -d (or pull amslertec/containly:2.7.4).
Full changelog: v2.7.3...v2.7.4
Containly 2.7.3
Fixed
- Compose override files are honoured everywhere. Containly called `docker compose -f `, and an explicit `-f` switches off Docker's automatic loading of `compose.override.yaml`. Services or settings defined only in an override file (for example a Mailpit service with its port mapping) were ignored by every deploy, pull, start, stop, restart, down and logs action from Containly, and a version update of such an image failed with "version tag not found". Containly now passes every compose file of a stack: the files Docker recorded in the `com.docker.compose.project.config_files` label of the stack's containers (in that order, plus a default override file that exists but is missing there, for example after an older Containly redeployed the stack with the main file only), or, before the first deploy, the main file plus `compose.override.yaml` / `.yml` / `docker-compose.override.yaml` / `.yml` exactly like `docker compose` does without `-f`. Only regular files inside the stack folder are used (no paths outside it, no symlinks). This applies to the stack actions, the app catalog, Git stacks, backups and restores, and stack migration.
- Version updates across compose files. Updating a pinned version tag now searches every
compose file of the stack and.env, and patches exactly the file(s) that define the tag (inline
or through a variable). When several files define it, all are updated together; if the deploy
fails, every changed file is restored. When nothing matches, the message names the files that
were searched.
Added
- Override files in the stack editor. When a stack has override files, the editor shows them as
tabs next to the main file (for examplecompose.yamlandcompose.override.yaml), with the same
checks, and the "Check before deploy" diff now includes saved changes in override files. A deploy
from the live terminal now also becomes the baseline for that diff. - API tokens (Free). Profile, API tokens creates personal access tokens with a name, scopes and
an optional expiry (30 days, 90 days, 1 year or none): Read (lists of hosts, containers,
stacks, images, updates, backups), Redeploy stacks (optionally limited to picked stacks) and
Prometheus metrics. The token is shown once; Containly stores only an HMAC of it. The list
shows the last use and IP; tokens can be revoked at any time, and admins see and revoke every
user's tokens under Settings, Integrations. Tokens are accepted only asAuthorization: Bearer
on an allow-list of API routes, never from a cookie, never for WebSockets, consoles, users,
settings or token management. A token never does more than its owner's current role: a viewer
cannot create a redeploy token, and tokens of a demoted, disabled or deleted user stop working.
Every action shows in the audit log as<user> (Token: <name>). No CSRF header is needed with
a bearer token; requests are rate limited per token. - CI redeploy API (Free).
POST /api/stacks/<id>/redeployorPOST /api/stacks/redeploywith
{"name": "<stack>", "endpoint": "<host id, optional>"}and a token with the Redeploy scope runs
git pullfor Git stacks,docker compose pullandup -dwith all compose files, and answers
202with a job;GET /api/jobs/<id>reportsqueued,running,okorerrorwith the last
output. A second request while a redeploy of the same stack runs returns the running job. At most
10 redeploys per minute per token. - Prometheus export (Free). Settings, Integrations, Prometheus switches on
GET /metrics
(text exposition format, off by default): endpoint up, host CPU, memory and disk, containers per
state, and per container state, health, restart count, CPU and memory, labelled with endpoint,
stack, container and image (no container IDs, image digests removed, at most 500 containers per
endpoint). The values come from the samples Containly already takes every minute, so a scrape
never calls Docker. A token with the Prometheus metrics scope is required unless the admin allows
open scrapes for a trusted network. The card shows a readyprometheus.ymlscrape config.
Inbound only: Containly sends nothing anywhere. - Demo: sample API tokens on the token pages (creating is disabled with a hint), the
Prometheus export shown switched off.
Security
- Token requests skip CSRF only because a bearer header cannot be sent cross-site without CORS,
which Containly never allows; a bearer header always wins over a session cookie, so the two are
never mixed. An invalid bearer token is refused also when open scrapes are allowed. - Migrations 48 (
stack_deploys.extra_files) and 49 (api_tokens).
Update: docker compose pull && docker compose up -d (image amslertec/containly:2.7.3 or latest).
Full changelog: v2.7.2...v2.7.3
Containly 2.7.2
Added
- Four more notification channels (Free). Settings, Notifications, Channels now also offers:
- Discord: paste the channel's webhook URL; messages arrive as an embed coloured by
severity (blue info, amber warning, red critical) with time, affected object and a link back
to Containly. Mentions are switched off and Markdown in names is escaped. - Telegram: your own bot token (from @Botfather), a chat ID (number,
-100...for groups
and channels, or@channelname) and an optional topic ID for groups with topics. HTML
formatting with escaped text and a link button text in the channel's language. - Slack: an incoming webhook URL; messages use blocks (header, text, context, an "Open in
Containly" button) inside an attachment coloured by severity, plus a plain fallback text. - Pushover: app token and user (or group) key, an optional device, and a priority per
severity from -2 to 2. Defaults are info -1, warning 0, critical 1; emergency priority 2 is
never a default, and choosing it shows a warning plus the repeat interval and stop time.
All four have the existing per-channel filter (minimum severity, events, message language),
"Send test" that saves nothing, delivery log, rate limit and retries.
- Discord: paste the channel's webhook URL; messages arrive as an embed coloured by
- Web push to your own devices (Free). Profile, Push notifications turns on push for the
current browser or the installed app (PWA) and lists every device of your account with rename,
minimum severity and event filter per device, "Send test" and remove. You get the
notifications for which you are a recipient under Settings, Notifications, also without an
e-mail address. Clicking a notification opens the relevant page in Containly. The page explains
why push is not available when the browser does not support it, the page is not served over
HTTPS, notifications are blocked, or an iPhone or iPad has not added Containly to the home
screen (Share, Add to Home Screen, iOS 16.4 or later). - Workflows (Pro): the notify step can also send push to devices (the recipients'
devices that pass their own minimum severity), next to in-app, e-mail and saved channels.
Checked on the server: on Free a workflow push is refused.
Security
- Discord and Slack webhook URLs are only accepted on the official hosts and paths
(https://discord.com/api/webhooks/..., alsodiscordapp.com,ptb.andcanary.;
https://hooks.slack.com/...); Telegram and Pushover requests only ever go to
api.telegram.organdapi.pushover.net. The URLs, bot token, app token and user key are
stored encrypted like all channel secrets and are never returned; the list shows a masked hint
(host and the last four characters of the webhook ID). - Web push needs no third-party service: Containly generates its own VAPID key pair once (private
key stored encrypted), encrypts every message for the receiving browser (RFC 8291, aes128gcm)
and posts it only to the browser vendors' push services (Google FCM, Mozilla, Apple, Microsoft
WNS). The push service only sees ciphertext. Subscription endpoints and keys are stored
encrypted and never returned by the API. A subscription is deleted as soon as the push service
answers 404 or 410 (app removed, permission revoked), and with its user. - Per device at most 20 push messages per minute; temporary errors are retried with the same
backoff as the channels.
Changed
- Database migration 47 adds the table
push_subscriptions; the VAPID key pair is stored in the
instance settings (webpush_vapid). - The channel type in the add dialog is now a dropdown (seven types).
- The demo shows sample Discord, Telegram, Slack and Pushover channels (test simulated, nothing is
sent); the push page is visible but disabled with a hint, because push needs your own instance
with HTTPS.
Notes
- The Discord, Telegram, Slack and Pushover request formats are verified against mock receivers
that check the vendor request shapes; no real vendor accounts were used before this release.
Web push was verified end to end with Google Chrome and the real FCM push service.
Containly 2.7.1
Added
- Notification channels (Free). Settings, Notifications has a new Channels card: send the
existing notifications to ntfy (ntfy.sh or your own server; access token or user and
password, priority per severity, optional click URL), Gotify (app token, priority per
severity) or a generic webhook (POST with a stable JSON body, optional HMAC-SHA256
signature, up to 10 custom headers). Every notification type that is switched on under Events
can go to channels; each channel has its own minimum severity, an optional subset of events
and a message language. - Send test in the add and edit dialog sends one real message with the entered settings and
saves nothing; the result is translated (for example "Sign-in refused (HTTP 401)" or "loopback
addresses are never allowed") and disappears as soon as a field changes. Saved channels have a
"Send test" button in the list. - Delivery log per channel (last 20 attempts: time, event, result, HTTP status, attempts;
never message content or secrets) and a status per channel (working, error, failing after 10
failed deliveries in a row). - Workflows (Pro): the notify step can pick saved channels next to in-app and e-mail. Using
channels in workflows needs Pro, checked on the server; normal notifications to channels stay
Free. - Generic webhook format (version 1):
{version, id, type, severity, title, message, target, link, at, test, source}with the headersX-Containly-Event,X-Containly-Deliveryand, when signed,
X-Containly-TimestampplusX-Containly-Signature: sha256=<hex HMAC-SHA256 of "<timestamp>.<body>">. Rate limit summaries use the typechannel.suppressedwith a
suppressedcount.
Security
- Channel secrets (tokens, passwords, signing secret, header values) are stored AES-256-GCM
encrypted with the instance master key and are never returned by the API, written to logs or
to the audit log. Changing the destination (scheme, host or port) of a saved channel requires
entering its secrets again, so a stored token can never be forwarded to another server, also
not by a draft test. - Outbound policy for channel URLs (SSRF): loopback,
localhost, link-local, cloud metadata
addresses (169.254.169.254, fd00:ec2::254, 100.100.100.200), reserved ranges and Containly's
own address and port are always refused; every resolved address is checked and the connection
is pinned to it (no DNS rebinding); no redirects are followed; plainhttp://is only allowed
to private network addresses. Private and LAN addresses are allowed by default because ntfy and
Gotify usually run there; the admin switch "Block private targets" (or
CONTAINLY_CHANNELS_BLOCK_PRIVATE=1, which forces it on) refuses them too. - Per channel at most 20 messages per minute; anything above is held back and summarised in one
message. Transient errors (HTTP 429, 5xx, timeouts, connection errors) are retried 3 times with
backoff (1 s, 2 s, 4 s, Retry-After honoured up to 30 s); other errors are not retried.
Changed
- Database migration 46 adds the tables
notification_channelsandnotification_deliveries. - The demo shows two sample channels with a delivery log; its test button is simulated and it
never sends anything.
Containly 2.7.0
Added
- Workflows (Pro). A new Workflows page automates routine reactions as typed rules:
a trigger, optional conditions (all or any) and ordered steps, with an "otherwise" branch.
No scripting and no free-form commands: triggers, conditions and steps are a fixed list.- Triggers: container unhealthy, container exited unexpectedly or killed for lack of memory
(OOM), restart loop, host offline (for a set time) and back online, sustained CPU or memory
above a threshold, backup finished (failed, succeeded or any result) and a schedule with the
app's own time picker (hourly, chosen weekdays, monthly). - Conditions: CPU or memory threshold, duration of the state, data freshness, host
reachable, backup target reachable and "not held by a running backup or restore". An unknown
value counts as not met, so nothing changes on stale data. - Steps: notify (in-app and/or e-mail, recipients under Settings, Notifications, Workflow
notifications), restart, start or stop a container, run a backup plan and wait for its
result, wait until healthy, update a container, and wait. - Backup before update: an update step only runs after a successful backup step earlier in
the same run. A failed backup, or one that finished with warnings, blocks the update, and the
run history says why. The update pulls the newest image for the container's tag and recreates
only that container. - Safety: changing steps act only on containers or stacks you pick explicitly, never on a
whole host. Each event is handled once per workflow, host and resource (the same container
name on another host is separate); per-resource cooldown; a circuit breaker (default three
changing actions per 60 minutes) pauses the workflow for that resource until an admin re-arms
it; a workflow's own actions never trigger another workflow; locked or offline hosts are
skipped; at most three runs at a time. Every action is written to the audit log as
"Workflow: ". Engine state (cooldowns, breakers, attempts, runs) survives restarts. - Templates and builder: 20 ready templates in six groups (Health and recovery, Backups,
Updates, Hosts, CPU and memory, Schedules and maintenance), for example unhealthy or crashed
container: notify and restart; restart loop alert; backup failed or succeeded; nightly or
weekly backup only when host and target are ready; weekly or monthly backup, update and health
check; host offline or back online; sustained CPU or memory alerts and restarts; weekly
restart, stop every night and start every morning. Every group starts closed and only one
group is open at a time (opening another closes the previous one), with mouse or keyboard.
Your own workflows are listed under their own "My workflows" heading with a count. The
builder shows the flow from trigger to steps, with an inline editor for each part. New
workflows start as drafts and do nothing until you pick their containers, hosts or plans and
enable them. - Simulate and history: a dry run checks the conditions against the live state and shows
which steps would run, without changing anything or sending notifications. "Run now" starts
the real steps once with every safety check. The history shows each run with its condition
results and step trace. - Only admins create, edit, enable or run workflows; viewers can read them. On Free the page
shows the templates as a read-only preview with an upgrade hint, and existing workflows pause
with reason "licence" until Pro is active again (they resume on their own). This is enforced
on the server, not only in the interface. - Available in English, German and Spanish.
- Triggers: container unhealthy, container exited unexpectedly or killed for lack of memory
- Demo: the public demo shows four sample workflows (two enabled, two drafts) with a short
run history, including a tripped circuit breaker. Simulate works there; "Run now" is refused
and the engine never acts on the simulated hosts.
Fixed
- German interface: two messages still written with "ß" (password rule "at least one uppercase
letter" and the migration warning about bind mounts outside the allowed paths) now use the
Swiss spelling ("ss").
Update: docker compose pull && docker compose up -d (image amslertec/containly:2.7.0), or the one-click self-update in the app.
Full changelog: v2.6.2...v2.7.0
Containly 2.6.2
Security
- Base image moved to Alpine 3.23. Both build stages of the app and demo images now use
node:22-alpine3.23pinned by digest (Node.js 22.23.3), with current OS package fixes applied
at build time. - Samba client 4.22.10 fixes CVE-2026-2340. The SMB client used for SMB backup targets moves
from 4.21.9 to 4.22.10-r0, the Alpine release that fixes CVE-2026-2340. SMB backups keep running
through the same confined, encrypted SMB3 client; it was re-tested against a real Samba server. - OpenLDAP libraries 2.6.13 (were 2.6.8), pulled in by the Samba client. Some scanners report
CVE-2026-22185 against this package; that issue is in the LMDBmdb_loadtool, which the
OpenLDAP package does not build and Containly never runs. - Known findings without an upstream fix: CVE-2025-60876 (BusyBox
wget) and CVE-2025-62813
(lz4) have no fixed package in Alpine yet. Containly does not callwget, and lz4 is only a
library dependency of the Samba client with no fixed release available; both will be picked up
automatically by the next image build once Alpine ships a fix.
Full changelog: v2.6.1...v2.6.2
Containly 2.6.1
Changed
- Grouped lists start closed. When a list is grouped (by stack, host, image, driver or
resource type), every group starts collapsed and shows only its header with the counts; a click
opens it. "Per page" now counts groups, so a page is a compact overview. Select all and keyboard
navigation only reach rows of opened groups. - Images grouped by image across hosts. In the Images view each repository is one group even
under All hosts (for examplealpineon five hosts is one group, the header says how many hosts),
and every row names its host. The images themselves stay separate per host. - Toolbar dropdowns share one row on phones. View, Sort and Per page sit side by side whenever
they fit, instead of wrapping onto a second row. On phones the View dropdown shows its icon and
value without the word "View", so the row also fits in German and Spanish.
Fixed
- The dashboard on phones could scroll sideways: a long event detail without spaces (for example
an image reference with a digest) widened the page. Long names and references on the dashboard
now wrap inside their cards.
Containly 2.6.0
Added
- View and grouping selector on every resource list. Containers can be shown as a list, by
Compose stack (plus a group for standalone containers) or, under All hosts, by host. Images can
be shown as a list, as Images (all tags and versions of a repository together; different
registries stay separate), by the stacks that use them or by host. Volumes and networks group by
stack (from their real Compose labels and the containers that use them, with Standalone and
Unused groups) or by host; networks keep the driver grouping. The backup overview groups by
stack, resource type or host. A resource shared by several stacks appears under each of them,
while selection and bulk actions still count it once (per host and ID); identical stack names on
different hosts never merge. The selector is one accessible dropdown, also on phones, the choice
is remembered per page, and switching from All hosts to a single host falls back from Host to
List. - Entra ID users show their sign-in name. The user list, user detail and profile show the
Entrapreferred_username(UPN) instead of the object ID. The immutable object ID stays the
only key that links a sign-in to an account and is shown separately as "Entra object ID" in the
account details. Existing Entra users get their sign-in name on their next sign-in. Local and
Entra accounts are never merged by e-mail or UPN.
Changed
- User detail. Reset password is only in the three-dot menu (no separate header button on
desktop or phone), the role and sign-in source badges sit side by side in one row, and the
username is shown once (below the display name only when it differs). - Container logs in English. All operator-facing Docker logs (startup, migrations, background
jobs, helpers, licence checks and the first-setup banner with the setup token) are English,
independent of the UI language. The UI and API messages stay translated. - Dark mode multi-select bar. The bulk action bar is a raised dark surface with a border in
dark mode instead of a bright white bar (containers, images, volumes, networks, stacks, hosts,
updates, users). Light mode is unchanged. - The support address is now support@containly.ch (README, Docker Hub page, security policy, the
licence file and the licence error messages).
Fixed
- Restart-loop notification never fired. The background monitor read the restart count from
the container list, which does not carry it, so every container counted as 0 restarts. The
count now comes from cached inspect data (refreshed only for running or restarting containers
that may have restarted), and the "restart loop" notification is sent again. - Phones: Most restarts card. Long container and host names no longer push the restart count
outside the card; the host sits under the name and long names wrap. - Phones: Compose editor. In the stack editor and the new stack dialog the editor frame stays
inside the screen; swiping sideways scrolls only the text, not the toolbar, dialog or page.
v2.5.2
Changed
- Demo start page in German, English and Spanish. The demo gateway now speaks the visitor's
language on every page it renders: start page, session ended, expired, maintenance, capacity
and rate-limit notices. A flag menu (Germany, United Kingdom, Spain) switches the language
with mouse or keyboard. The language is picked from an explicit?lang=link parameter
(the website passes its current language), then the stored demo choice, then the browser
language, then English. The chosen language is carried into the demo session, so the app
opens in it, and an explicit language change inside the demo is kept across logout, restart
and expiry.
Fixed
- The demo start page was always German for visitors whose browser or website language was not
German, because the website language never reached the demo host.
v2.5.1
Added
- Test connection in backup target dialogs. New and edit dialogs for all six target types
(local folder, host folder, S3, SFTP, SMB, NFS) have an optional "Test connection" button that
checks the unsaved settings before you save. The test writes a uniquely named object, reads it
back, compares it and removes it again; it never creates folders, never overwrites existing
files, and reports a failed clean-up instead of claiming success. Nothing is saved, pinned or
recorded by a test. A stored secret is only reused for the same target and destination; if you
change the destination, enter the secret again. The result is cleared when a field changes. In
the demo the test is simulated without network access. - "With backups" filter. The backup overview has a new segment after "All" that lists only
hosts and resources that have at least one complete backup set (English, German, Spanish). The
counts follow the selected host and search.
Changed
- The connection test of saved targets uses the same safer probe (exclusive test object, clean-up
reporting, translated error messages).
Fixed
- Backup list: icons, names and the metadata line no longer touch the row borders; rows keep
even vertical spacing and centred actions.