Containers Up! is a web-based container management platform designed to simplify the administration of containers across multiple remote hosts.
It provides a unified interface for managing containerized applications, and automating updates with minimal manual intervention.
- π₯οΈ Multi-Host Management: Manage containers on multiple hosts via SSH connections
- ποΈ Granular Control:
- π¦ Control stacks/services via compose files
- π³ Manage individual containers
- πΌοΈ Container image management
- π Container log viewer
- ποΈ Cleanup of unused images
- π Automated Updates: Container updates via GitHub webhooks (via Dependabot pull requests) & image tag updates via a schedule
- π© Notifications: When a new Dependabot PR is created or a new container image is available (via Apprise)
- π Service Discovery: Display web app icons and URLs (via existing Traefik labels)
- π§Ή Resource Management: Cleanup of older images
- π± Responsive Design: Works seamlessly on desktop and mobile devices
- π Modern UX: Automatic light and dark mode (based on system settings)
- π Job Tracking: Monitor update jobs with detailed logs and retry capabilities
- π Security: Secure SSH connections, webhook signature verification and OIDC authentication
The app can be started using the following compose.yml:
services:
containers-up:
# https://github.com/DigitallyRefined/containers-up/releases
image: ghcr.io/digitallyrefined/containers-up:1.1.0
restart: unless-stopped
ports:
- 3000:3000
- 3001:3001
volumes:
- ./storage:/storage
- ./storage/.ssh:/root/.ssh
- ./storage/.docker:/root/.dockerOpen http://localhost:3000 to set up a new host. Set a name, SSH host and private key to display a dashboard of running composed containers, individual containers, images and actions.
Optional system wide configuration can be changed by copying .env.default to .env and adding env_file: ./.env to the compose file.
compose.yml example with HTTPS & OIDC authentication (via Pocket ID & Traefik)
services:
containers-up:
# https://github.com/DigitallyRefined/containers-up/releases
image: ghcr.io/digitallyrefined/containers-up:1.1.0
restart: unless-stopped
volumes:
- ./containers-up/storage:/storage
- ./containers-up/storage/.ssh:/root/.ssh
- ./containers-up/storage/.docker:/root/.docker
env_file:
- ./.env # < Create this file based on the .env.default instructions
networks:
- traefik
labels:
traefik.enable: true
traefik.http.routers.containers-up.entrypoints: websecure
traefik.http.routers.containers-up.rule: Host(`containers-up.example.com`) # < Update this
traefik.http.routers.containers-up.tls: true
traefik.http.routers.containers-up.tls.certresolver: production-cloudflare-dns
traefik.http.routers.containers-up.service: containers-up
traefik.http.services.containers-up.loadbalancer.server.port: 3000
traefik.http.routers.containers-up-webhook.entrypoints: websecure
traefik.http.routers.containers-up-webhook.rule: Host(`containers-up.example.com`) && PathPrefix(`/api/webhook`) # < Update this
traefik.http.routers.containers-up-webhook.tls: true
traefik.http.routers.containers-up-webhook.tls.certresolver: production-cloudflare-dns
traefik.http.routers.containers-up-webhook.service: containers-up-webhook
traefik.http.services.containers-up-webhook.loadbalancer.server.port: 3001
pocket-id:
# https://github.com/pocket-id/pocket-id/releases
image: ghcr.io/pocket-id/pocket-id:v1.13.1
restart: unless-stopped
volumes:
- './pocket-id/data:/app/data'
environment:
- APP_URL=https://id.example.com # < Update this
- TRUST_PROXY=true
networks:
- 'traefik'
labels:
traefik.enable: true
traefik.http.routers.pocketid.entrypoints: websecure
traefik.http.routers.pocketid.rule: Host(`id.example.com`) # < Update this
traefik.http.routers.pocketid.tls: true
traefik.http.routers.pocketid.tls.certresolver: production-cloudflare-dns
traefik:
# Check migration guide first: https://doc.traefik.io/traefik/master/migration/v3/
# https://github.com/traefik/traefik/releases
image: docker.io/traefik:3.5.0
container_name: 'traefik'
restart: unless-stopped
ports:
- '80:80'
- '443:443' # To setup HTTPS see: https://www.youtube.com/watch?v=-hfejNXqOzA
volumes:
- ./traefik/config:/etc/traefik
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- 'traefik'
networks:
traefik:
external: true- Create the network
docker network create traefikand start the servicesdocker compose up -d - Once Traefik and Pocket ID are up and running, set up a new user via Pocket ID and optionally add them to an admin group
- In the Pocket ID admin account create a new OIDC client and set up the callback URL as
https://containers-up.example.com/auth-callback*and optionally only allow the admin group - In the Containers Up!
.envfile (see.env.default) uncomment the OIDC config section, add the URI of Pocket ID (without any trailing paths) then copy and paste the client ID and secret (JWKS certificate URL can be set manually if auto-discovery fails viaOIDC_JWKS_URL) - After restarting the app, accessing
https://containers-up.example.comshould now require you to login
-
Make sure that your Containers Up! instance is available online publicly via HTTPS, sharing only the webhook port
3001. E.g. via a Cloudflare Tunnel or a Docker Wireguard Tunnel -
Create a GitHub repository with your container
compose.ymlfiles -
Each of your
compose.ymlfiles must use the full image version (not:latest) to receive Dependabot updates -
Under the Settings > Actions, enable Allow all actions and reusable workflows and under Workflow permissions allow Read and write permissions
-
In your repo add
.github/dependabot.template.yml:
version: 2
enable-beta-ecosystems: true # Remove once docker-compose updates become stable
updates:
- package-ecosystem: 'docker-compose'
directory: '**/compose.yml' # change this based on if you call your files compose.yml or docker-compose.yml
- package-ecosystem: 'github-actions'
directory: '/'- Create a
.github/workflows/generate_dependabot.ymlfile with the following content:
name: Generate dependabot.yml
on:
push:
branches:
- main
repository_dispatch:
workflow_dispatch:
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Generate dependabot.yml
uses: Makeshift/generate-dependabot-glob-action@master
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7-
This will automatically create a PR that will create a GitHub action managed
.github/dependabot.ymlfile, which will automatically be updated for each of yourcompose.ymlfiles. If it doesn't, click Actions > Generate dependabot.yml > Run workflow -
Next edit your host in Containers Up! and add the working folder where your repo is checked out on your server and add your GitHub repo URL
user/repo(withouthttps://github.com/), generate a random webhook secret and click the βΉοΈ info icon copy the base URL to the webhook -
Back on GitHub, go to Settings > Webhooks > Add webhook, add your public webhook domain and base URL (listed on the Containers Up! edit webhook info screen) and select
application/jsonas the Content Type. Use the same random webhook secret from your repo settings and choose Let me select individual events > Pull requests
If everything has been set up correctly the next time Dependabot creates a PR to update a compose.yml file an update will also appear on the Containers Up! dashboard.
All environment variables are optional and can be set in the compose.yml file via an env_file: ./.env or using the environment: array. Environment variables starting with ENV_PUBLIC_ are also embedded in the public HTML output.
| Key | Description | Default |
|---|---|---|
COMPOSE_FILENAME |
The default compose file to look for in the folder (usually compose.yml or docker-compose.yml) |
compose.yml |
APP_URL |
App URL to used by links in notifications | |
APPRISE_NOTIFICATION |
Apprise is used for container update notifications. See Apprise syntax, see: Apprise Supported Notifications syntax | |
SSH_CONTROL_PERSIST |
How long SSH connections should persist after last request | 20m |
ENV_PUBLIC_OIDC_ISSUER_URI |
OpenID Connect base URI | Auth disabled |
ENV_PUBLIC_OIDC_CLIENT_ID |
OpenID Connect client ID | |
OIDC_CLIENT_SECRET |
OpenID Connect client secret | |
OIDC_JWKS_URL |
Optional OpenID Connect JSON Web Key Set (file URL) | Auto discovered |
MAX_QUEUE_TIME_MINS |
Max time in minutes a queued update can wait for | 10 |
LOG_LINES |
Number of previous log lines shown when viewing a containers logs | 500 |
DOCKER_USERNAME |
Docker Hub username - used to check for image updates (use if rate limited by Docker) | |
DOCKER_TOKEN |
Docker Hub token | |
GHCR_USERNAME |
GitHub Container Registry username - used to check for image updates on GHCR (use if rate limited by GitHub) | |
GHCR_TOKEN |
GitHub Container Registry token | |
CONTAINER_REGISTRY_USERNAME |
Custom container image registry username | |
CONTAINER_REGISTRY_TOKEN |
Custom container image registry token |
