Showing preview only (630K chars total). Download the full file or copy to clipboard to get everything.
Repository: containrrr/watchtower
Branch: main
Commit: ca0e86e824ec
Files: 165
Total size: 19.7 MB
Directory structure:
gitextract_hq806g1o/
├── .all-contributorsrc
├── .codacy.yml
├── .devbots/
│ └── lock-issue.yml
├── .editorconfig
├── .github/
│ ├── CODEOWNERS
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug.yml
│ │ ├── config.yml
│ │ └── feature_request.yml
│ ├── dependabot.yml
│ ├── pull_request_template.md
│ ├── stale.yml
│ └── workflows/
│ ├── codeql-analysis.yml
│ ├── dependabot-approve.yml
│ ├── greetings.yml
│ ├── publish-docs.yml
│ ├── pull-request.yml
│ ├── release-dev.yaml
│ └── release.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── SECURITY.md
├── build.sh
├── cmd/
│ ├── notify-upgrade.go
│ └── root.go
├── code_of_conduct.md
├── docker-compose.yml
├── dockerfiles/
│ ├── Dockerfile
│ ├── Dockerfile.dev-self-contained
│ ├── Dockerfile.self-contained
│ └── container-networking/
│ └── docker-compose.yml
├── docs/
│ ├── arguments.md
│ ├── container-selection.md
│ ├── http-api-mode.md
│ ├── index.md
│ ├── introduction.md
│ ├── lifecycle-hooks.md
│ ├── linked-containers.md
│ ├── metrics.md
│ ├── notifications.md
│ ├── private-registries.md
│ ├── remote-hosts.md
│ ├── running-multiple-instances.md
│ ├── secure-connections.md
│ ├── stop-signals.md
│ ├── stylesheets/
│ │ └── theme.css
│ ├── template-preview.md
│ ├── updating.md
│ └── usage-overview.md
├── docs-requirements.txt
├── go.mod
├── go.sum
├── goreleaser.yml
├── grafana/
│ ├── dashboards/
│ │ ├── dashboard.json
│ │ └── dashboard.yml
│ └── datasources/
│ └── datasource.yml
├── internal/
│ ├── actions/
│ │ ├── actions_suite_test.go
│ │ ├── check.go
│ │ ├── mocks/
│ │ │ ├── client.go
│ │ │ ├── container.go
│ │ │ └── progress.go
│ │ ├── update.go
│ │ └── update_test.go
│ ├── flags/
│ │ ├── flags.go
│ │ └── flags_test.go
│ ├── meta/
│ │ └── meta.go
│ └── util/
│ ├── rand_name.go
│ ├── rand_sha256.go
│ ├── util.go
│ └── util_test.go
├── main.go
├── mkdocs.yml
├── oryxBuildBinary
├── pkg/
│ ├── api/
│ │ ├── api.go
│ │ ├── api_test.go
│ │ ├── metrics/
│ │ │ ├── metrics.go
│ │ │ └── metrics_test.go
│ │ └── update/
│ │ └── update.go
│ ├── container/
│ │ ├── cgroup_id.go
│ │ ├── cgroup_id_test.go
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── container.go
│ │ ├── container_mock_test.go
│ │ ├── container_suite_test.go
│ │ ├── container_test.go
│ │ ├── errors.go
│ │ ├── metadata.go
│ │ ├── mocks/
│ │ │ ├── ApiServer.go
│ │ │ ├── FilterableContainer.go
│ │ │ ├── container_ref.go
│ │ │ └── data/
│ │ │ ├── container_net_consumer-missing_supplier.json
│ │ │ ├── container_net_consumer.json
│ │ │ ├── container_net_supplier.json
│ │ │ ├── container_restarting.json
│ │ │ ├── container_running.json
│ │ │ ├── container_stopped.json
│ │ │ ├── container_watchtower.json
│ │ │ ├── containers.json
│ │ │ ├── image_default.json
│ │ │ ├── image_net_consumer.json
│ │ │ ├── image_net_producer.json
│ │ │ └── image_running.json
│ │ └── util_test.go
│ ├── filters/
│ │ ├── filters.go
│ │ └── filters_test.go
│ ├── lifecycle/
│ │ └── lifecycle.go
│ ├── metrics/
│ │ └── metrics.go
│ ├── notifications/
│ │ ├── common_templates.go
│ │ ├── email.go
│ │ ├── gotify.go
│ │ ├── json.go
│ │ ├── json_test.go
│ │ ├── model.go
│ │ ├── msteams.go
│ │ ├── notifications_suite_test.go
│ │ ├── notifier.go
│ │ ├── notifier_test.go
│ │ ├── preview/
│ │ │ ├── data/
│ │ │ │ ├── data.go
│ │ │ │ ├── logs.go
│ │ │ │ ├── preview_strings.go
│ │ │ │ ├── report.go
│ │ │ │ └── status.go
│ │ │ └── tplprev.go
│ │ ├── shoutrrr.go
│ │ ├── shoutrrr_test.go
│ │ ├── slack.go
│ │ └── templates/
│ │ └── funcs.go
│ ├── registry/
│ │ ├── auth/
│ │ │ ├── auth.go
│ │ │ └── auth_test.go
│ │ ├── digest/
│ │ │ ├── digest.go
│ │ │ └── digest_test.go
│ │ ├── helpers/
│ │ │ ├── helpers.go
│ │ │ └── helpers_test.go
│ │ ├── manifest/
│ │ │ ├── manifest.go
│ │ │ └── manifest_test.go
│ │ ├── registry.go
│ │ ├── registry_suite_test.go
│ │ ├── registry_test.go
│ │ ├── trust.go
│ │ └── trust_test.go
│ ├── session/
│ │ ├── container_status.go
│ │ ├── progress.go
│ │ └── report.go
│ ├── sorter/
│ │ └── sort.go
│ └── types/
│ ├── container.go
│ ├── convertible_notifier.go
│ ├── filter.go
│ ├── filterable_container.go
│ ├── notifier.go
│ ├── registry_credentials.go
│ ├── report.go
│ ├── token_response.go
│ └── update_params.go
├── prometheus/
│ └── prometheus.yml
├── scripts/
│ ├── build-tplprev.sh
│ ├── codecov.sh
│ ├── contnet-tests.sh
│ ├── dependency-test.sh
│ ├── docker-util.sh
│ ├── du-cli.sh
│ └── lifecycle-tests.sh
└── tplprev/
├── main.go
└── main_wasm.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .all-contributorsrc
================================================
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "piksel",
"name": "nils måsén",
"avatar_url": "https://avatars2.githubusercontent.com/u/807383?v=4",
"profile": "https://piksel.se",
"contributions": [
"code",
"doc",
"maintenance",
"review"
]
},
{
"login": "simskij",
"name": "Simon Aronsson",
"avatar_url": "https://avatars0.githubusercontent.com/u/1596025?v=4",
"profile": "http://simme.dev",
"contributions": [
"code",
"doc",
"maintenance",
"review"
]
},
{
"login": "Codelica",
"name": "James",
"avatar_url": "https://avatars3.githubusercontent.com/u/386101?v=4",
"profile": "http://codelica.com",
"contributions": [
"test",
"ideas"
]
},
{
"login": "KopfKrieg",
"name": "Florian",
"avatar_url": "https://avatars2.githubusercontent.com/u/5047813?v=4",
"profile": "https://kopfkrieg.org",
"contributions": [
"review",
"doc"
]
},
{
"login": "bdehamer",
"name": "Brian DeHamer",
"avatar_url": "https://avatars1.githubusercontent.com/u/398027?v=4",
"profile": "https://github.com/bdehamer",
"contributions": [
"code",
"maintenance"
]
},
{
"login": "rosscado",
"name": "Ross Cadogan",
"avatar_url": "https://avatars1.githubusercontent.com/u/16578183?v=4",
"profile": "https://github.com/rosscado",
"contributions": [
"code"
]
},
{
"login": "stffabi",
"name": "stffabi",
"avatar_url": "https://avatars0.githubusercontent.com/u/9464631?v=4",
"profile": "https://github.com/stffabi",
"contributions": [
"code",
"maintenance"
]
},
{
"login": "ATCUSA",
"name": "Austin",
"avatar_url": "https://avatars3.githubusercontent.com/u/3581228?v=4",
"profile": "https://github.com/ATCUSA",
"contributions": [
"doc"
]
},
{
"login": "davidgardner11",
"name": "David Gardner",
"avatar_url": "https://avatars2.githubusercontent.com/u/6181487?v=4",
"profile": "https://labs.ctl.io",
"contributions": [
"review",
"doc"
]
},
{
"login": "dolanor",
"name": "Tanguy ⧓ Herrmann",
"avatar_url": "https://avatars3.githubusercontent.com/u/928722?v=4",
"profile": "https://github.com/dolanor",
"contributions": [
"code"
]
},
{
"login": "rdamazio",
"name": "Rodrigo Damazio Bovendorp",
"avatar_url": "https://avatars3.githubusercontent.com/u/997641?v=4",
"profile": "https://github.com/rdamazio",
"contributions": [
"code",
"doc"
]
},
{
"login": "thelamer",
"name": "Ryan Kuba",
"avatar_url": "https://avatars3.githubusercontent.com/u/1852688?v=4",
"profile": "https://www.taisun.io/",
"contributions": [
"infra"
]
},
{
"login": "cnrmck",
"name": "cnrmck",
"avatar_url": "https://avatars2.githubusercontent.com/u/22061955?v=4",
"profile": "https://github.com/cnrmck",
"contributions": [
"doc"
]
},
{
"login": "haswalt",
"name": "Harry Walter",
"avatar_url": "https://avatars3.githubusercontent.com/u/338588?v=4",
"profile": "http://harrywalter.co.uk",
"contributions": [
"code"
]
},
{
"login": "Robotex",
"name": "Robotex",
"avatar_url": "https://avatars3.githubusercontent.com/u/74515?v=4",
"profile": "http://projectsperanza.com",
"contributions": [
"doc"
]
},
{
"login": "ubergesundheit",
"name": "Gerald Pape",
"avatar_url": "https://avatars0.githubusercontent.com/u/1494211?v=4",
"profile": "http://geraldpape.io",
"contributions": [
"doc"
]
},
{
"login": "fomk",
"name": "fomk",
"avatar_url": "https://avatars0.githubusercontent.com/u/17636183?v=4",
"profile": "https://github.com/fomk",
"contributions": [
"code"
]
},
{
"login": "svengo",
"name": "Sven Gottwald",
"avatar_url": "https://avatars3.githubusercontent.com/u/2502366?v=4",
"profile": "https://github.com/svengo",
"contributions": [
"infra"
]
},
{
"login": "techknowlogick",
"name": "techknowlogick",
"avatar_url": "https://avatars1.githubusercontent.com/u/164197?v=4",
"profile": "https://liberapay.com/techknowlogick/",
"contributions": [
"code"
]
},
{
"login": "waja",
"name": "waja",
"avatar_url": "https://avatars1.githubusercontent.com/u/1449568?v=4",
"profile": "http://log.c5t.org/about/",
"contributions": [
"doc"
]
},
{
"login": "salbertson",
"name": "Scott Albertson",
"avatar_url": "https://avatars2.githubusercontent.com/u/154463?v=4",
"profile": "http://scottalbertson.com",
"contributions": [
"doc"
]
},
{
"login": "huddlesj",
"name": "Jason Huddleston",
"avatar_url": "https://avatars1.githubusercontent.com/u/11966535?v=4",
"profile": "https://github.com/huddlesj",
"contributions": [
"doc"
]
},
{
"login": "napstr",
"name": "Napster",
"avatar_url": "https://avatars3.githubusercontent.com/u/6048348?v=4",
"profile": "https://npstr.space/",
"contributions": [
"code"
]
},
{
"login": "darknode",
"name": "Maxim",
"avatar_url": "https://avatars1.githubusercontent.com/u/809429?v=4",
"profile": "https://github.com/darknode",
"contributions": [
"code",
"doc"
]
},
{
"login": "mxschmitt",
"name": "Max Schmitt",
"avatar_url": "https://avatars0.githubusercontent.com/u/17984549?v=4",
"profile": "https://schmitt.cat",
"contributions": [
"doc"
]
},
{
"login": "cron410",
"name": "cron410",
"avatar_url": "https://avatars1.githubusercontent.com/u/3082899?v=4",
"profile": "https://github.com/cron410",
"contributions": [
"doc"
]
},
{
"login": "Cardoso222",
"name": "Paulo Henrique",
"avatar_url": "https://avatars3.githubusercontent.com/u/7026517?v=4",
"profile": "https://github.com/Cardoso222",
"contributions": [
"doc"
]
},
{
"login": "belak",
"name": "Kaleb Elwert",
"avatar_url": "https://avatars0.githubusercontent.com/u/107097?v=4",
"profile": "https://coded.io",
"contributions": [
"doc"
]
},
{
"login": "wmbutler",
"name": "Bill Butler",
"avatar_url": "https://avatars1.githubusercontent.com/u/1254810?v=4",
"profile": "https://github.com/wmbutler",
"contributions": [
"doc"
]
},
{
"login": "mariotacke",
"name": "Mario Tacke",
"avatar_url": "https://avatars2.githubusercontent.com/u/4942019?v=4",
"profile": "https://www.mariotacke.io",
"contributions": [
"code"
]
},
{
"login": "mrw34",
"name": "Mark Woodbridge",
"avatar_url": "https://avatars2.githubusercontent.com/u/1101318?v=4",
"profile": "https://markwoodbridge.com",
"contributions": [
"code"
]
},
{
"login": "Ansem93",
"name": "Ansem93",
"avatar_url": "https://avatars3.githubusercontent.com/u/6626218?v=4",
"profile": "https://github.com/Ansem93",
"contributions": [
"doc"
]
},
{
"login": "lukapeschke",
"name": "Luka Peschke",
"avatar_url": "https://avatars1.githubusercontent.com/u/17085536?v=4",
"profile": "https://github.com/lukapeschke",
"contributions": [
"code",
"doc"
]
},
{
"login": "zoispag",
"name": "Zois Pagoulatos",
"avatar_url": "https://avatars0.githubusercontent.com/u/21138205?v=4",
"profile": "https://github.com/zoispag",
"contributions": [
"code",
"review",
"maintenance"
]
},
{
"login": "alexandremenif",
"name": "Alexandre Menif",
"avatar_url": "https://avatars0.githubusercontent.com/u/16152103?v=4",
"profile": "https://alexandre.menif.name",
"contributions": [
"code"
]
},
{
"login": "chugunov",
"name": "Andrey",
"avatar_url": "https://avatars1.githubusercontent.com/u/4140479?v=4",
"profile": "https://github.com/chugunov",
"contributions": [
"doc"
]
},
{
"login": "noplanman",
"name": "Armando Lüscher",
"avatar_url": "https://avatars3.githubusercontent.com/u/9423417?v=4",
"profile": "https://noplanman.ch",
"contributions": [
"doc"
]
},
{
"login": "rjbudke",
"name": "Ryan Budke",
"avatar_url": "https://avatars2.githubusercontent.com/u/273485?v=4",
"profile": "https://github.com/rjbudke",
"contributions": [
"doc"
]
},
{
"login": "kaloyan-raev",
"name": "Kaloyan Raev",
"avatar_url": "https://avatars2.githubusercontent.com/u/468091?v=4",
"profile": "http://kaloyan.raev.name",
"contributions": [
"code",
"test"
]
},
{
"login": "sixth",
"name": "sixth",
"avatar_url": "https://avatars3.githubusercontent.com/u/11591445?v=4",
"profile": "https://github.com/sixth",
"contributions": [
"doc"
]
},
{
"login": "foosel",
"name": "Gina Häußge",
"avatar_url": "https://avatars0.githubusercontent.com/u/83657?v=4",
"profile": "https://foosel.net",
"contributions": [
"code"
]
},
{
"login": "8ear",
"name": "Max H.",
"avatar_url": "https://avatars0.githubusercontent.com/u/10329648?v=4",
"profile": "https://github.com/8ear",
"contributions": [
"code"
]
},
{
"login": "pjknkda",
"name": "Jungkook Park",
"avatar_url": "https://avatars0.githubusercontent.com/u/4986524?v=4",
"profile": "https://pjknkda.github.io",
"contributions": [
"doc"
]
},
{
"login": "jnidzwetzki",
"name": "Jan Kristof Nidzwetzki",
"avatar_url": "https://avatars1.githubusercontent.com/u/5753622?v=4",
"profile": "https://achfrag.net",
"contributions": [
"doc"
]
},
{
"login": "mindrunner",
"name": "lukas",
"avatar_url": "https://avatars0.githubusercontent.com/u/1413542?v=4",
"profile": "https://www.lukaselsner.de",
"contributions": [
"code"
]
},
{
"login": "codingCoffee",
"name": "Ameya Shenoy",
"avatar_url": "https://avatars3.githubusercontent.com/u/13611153?v=4",
"profile": "https://codingcoffee.dev",
"contributions": [
"code"
]
},
{
"login": "raymondelooff",
"name": "Raymon de Looff",
"avatar_url": "https://avatars0.githubusercontent.com/u/9716806?v=4",
"profile": "https://github.com/raymondelooff",
"contributions": [
"code"
]
},
{
"login": "jsclayton",
"name": "John Clayton",
"avatar_url": "https://avatars2.githubusercontent.com/u/704034?v=4",
"profile": "http://codemonkeylabs.com",
"contributions": [
"code"
]
},
{
"login": "Germs2004",
"name": "Germs2004",
"avatar_url": "https://avatars2.githubusercontent.com/u/5519340?v=4",
"profile": "https://github.com/Germs2004",
"contributions": [
"doc"
]
},
{
"login": "lukwil",
"name": "Lukas Willburger",
"avatar_url": "https://avatars1.githubusercontent.com/u/30203234?v=4",
"profile": "https://github.com/lukwil",
"contributions": [
"code"
]
},
{
"login": "auanasgheps",
"name": "Oliver Cervera",
"avatar_url": "https://avatars2.githubusercontent.com/u/20586878?v=4",
"profile": "https://github.com/auanasgheps",
"contributions": [
"doc"
]
},
{
"login": "victorcmoura",
"name": "Victor Moura",
"avatar_url": "https://avatars1.githubusercontent.com/u/26290053?v=4",
"profile": "https://github.com/victorcmoura",
"contributions": [
"test",
"code",
"doc"
]
},
{
"login": "mbrandau",
"name": "Maximilian Brandau",
"avatar_url": "https://avatars3.githubusercontent.com/u/12972798?v=4",
"profile": "https://github.com/mbrandau",
"contributions": [
"code",
"test"
]
},
{
"login": "aneisch",
"name": "Andrew",
"avatar_url": "https://avatars1.githubusercontent.com/u/6991461?v=4",
"profile": "https://github.com/aneisch",
"contributions": [
"doc"
]
},
{
"login": "sixcorners",
"name": "sixcorners",
"avatar_url": "https://avatars0.githubusercontent.com/u/585501?v=4",
"profile": "https://github.com/sixcorners",
"contributions": [
"doc"
]
},
{
"login": "arnested",
"name": "Arne Jørgensen",
"avatar_url": "https://avatars2.githubusercontent.com/u/190005?v=4",
"profile": "https://arnested.dk",
"contributions": [
"test",
"review"
]
},
{
"login": "patski123",
"name": "PatSki123",
"avatar_url": "https://avatars1.githubusercontent.com/u/19295295?v=4",
"profile": "https://github.com/patski123",
"contributions": [
"doc"
]
},
{
"login": "Saicheg",
"name": "Valentine Zavadsky",
"avatar_url": "https://avatars2.githubusercontent.com/u/624999?v=4",
"profile": "https://rubyroidlabs.com/",
"contributions": [
"code",
"doc",
"test"
]
},
{
"login": "bopoh24",
"name": "Alexander Voronin",
"avatar_url": "https://avatars2.githubusercontent.com/u/4086631?v=4",
"profile": "https://github.com/bopoh24",
"contributions": [
"code",
"bug"
]
},
{
"login": "ogmueller",
"name": "Oliver Mueller",
"avatar_url": "https://avatars0.githubusercontent.com/u/788989?v=4",
"profile": "http://www.teqneers.de",
"contributions": [
"doc"
]
},
{
"login": "tammert",
"name": "Sebastiaan Tammer",
"avatar_url": "https://avatars0.githubusercontent.com/u/8885250?v=4",
"profile": "https://github.com/tammert",
"contributions": [
"code"
]
},
{
"login": "miosame",
"name": "miosame",
"avatar_url": "https://avatars1.githubusercontent.com/u/8201077?v=4",
"profile": "https://github.com/Miosame",
"contributions": [
"doc"
]
},
{
"login": "andrewjmetzger",
"name": "Andrew Metzger",
"avatar_url": "https://avatars3.githubusercontent.com/u/590246?v=4",
"profile": "https://mtz.gr",
"contributions": [
"bug",
"example"
]
},
{
"login": "pgrimaud",
"name": "Pierre Grimaud",
"avatar_url": "https://avatars1.githubusercontent.com/u/1866496?v=4",
"profile": "https://github.com/pgrimaud",
"contributions": [
"doc"
]
},
{
"login": "mattdoran",
"name": "Matt Doran",
"avatar_url": "https://avatars0.githubusercontent.com/u/577779?v=4",
"profile": "https://github.com/mattdoran",
"contributions": [
"doc"
]
},
{
"login": "MihailITPlace",
"name": "MihailITPlace",
"avatar_url": "https://avatars2.githubusercontent.com/u/28401551?v=4",
"profile": "https://github.com/MihailITPlace",
"contributions": [
"code"
]
},
{
"login": "bugficks",
"name": "bugficks",
"avatar_url": "https://avatars1.githubusercontent.com/u/2992895?v=4",
"profile": "https://github.com/bugficks",
"contributions": [
"code",
"doc"
]
},
{
"login": "MichaelSp",
"name": "Michael",
"avatar_url": "https://avatars0.githubusercontent.com/u/448282?v=4",
"profile": "https://github.com/MichaelSp",
"contributions": [
"code"
]
},
{
"login": "jokay",
"name": "D. Domig",
"avatar_url": "https://avatars0.githubusercontent.com/u/18613935?v=4",
"profile": "https://github.com/jokay",
"contributions": [
"doc"
]
},
{
"login": "osheroff",
"name": "Ben Osheroff",
"avatar_url": "https://avatars1.githubusercontent.com/u/260084?v=4",
"profile": "https://maxwells-daemon.io",
"contributions": [
"code"
]
},
{
"login": "dhet",
"name": "David H.",
"avatar_url": "https://avatars3.githubusercontent.com/u/2668621?v=4",
"profile": "https://github.com/dhet",
"contributions": [
"code"
]
},
{
"login": "chander",
"name": "Chander Ganesan",
"avatar_url": "https://avatars1.githubusercontent.com/u/671887?v=4",
"profile": "http://www.gridgeo.com",
"contributions": [
"doc"
]
},
{
"login": "yrien30",
"name": "yrien30",
"avatar_url": "https://avatars1.githubusercontent.com/u/26816162?v=4",
"profile": "https://github.com/yrien30",
"contributions": [
"code"
]
},
{
"login": "ksurl",
"name": "ksurl",
"avatar_url": "https://avatars1.githubusercontent.com/u/1371562?v=4",
"profile": "https://github.com/ksurl",
"contributions": [
"doc",
"code",
"infra"
]
},
{
"login": "rg9400",
"name": "rg9400",
"avatar_url": "https://avatars2.githubusercontent.com/u/39887349?v=4",
"profile": "https://github.com/rg9400",
"contributions": [
"code"
]
},
{
"login": "tkalus",
"name": "Turtle Kalus",
"avatar_url": "https://avatars2.githubusercontent.com/u/287181?v=4",
"profile": "https://github.com/tkalus",
"contributions": [
"code"
]
},
{
"login": "SrihariThalla",
"name": "Srihari Thalla",
"avatar_url": "https://avatars1.githubusercontent.com/u/7479937?v=4",
"profile": "https://github.com/SrihariThalla",
"contributions": [
"doc"
]
},
{
"login": "nymous",
"name": "Thomas Gaudin",
"avatar_url": "https://avatars1.githubusercontent.com/u/4216559?v=4",
"profile": "https://nymous.io",
"contributions": [
"doc"
]
},
{
"login": "hydrargyrum",
"name": "hydrargyrum",
"avatar_url": "https://avatars.githubusercontent.com/u/2804645?v=4",
"profile": "https://indigo.re/",
"contributions": [
"doc"
]
},
{
"login": "reinout",
"name": "Reinout van Rees",
"avatar_url": "https://avatars.githubusercontent.com/u/121433?v=4",
"profile": "https://reinout.vanrees.org",
"contributions": [
"doc"
]
},
{
"login": "DasSkelett",
"name": "DasSkelett",
"avatar_url": "https://avatars.githubusercontent.com/u/28812678?v=4",
"profile": "https://github.com/DasSkelett",
"contributions": [
"code"
]
},
{
"login": "zenjabba",
"name": "zenjabba",
"avatar_url": "https://avatars.githubusercontent.com/u/679864?v=4",
"profile": "https://github.com/zenjabba",
"contributions": [
"doc"
]
},
{
"login": "djquan",
"name": "Dan Quan",
"avatar_url": "https://avatars.githubusercontent.com/u/3526705?v=4",
"profile": "https://quan.io",
"contributions": [
"doc"
]
},
{
"login": "modem7",
"name": "modem7",
"avatar_url": "https://avatars.githubusercontent.com/u/4349962?v=4",
"profile": "https://github.com/modem7",
"contributions": [
"doc"
]
},
{
"login": "hypnoglow",
"name": "Igor Zibarev",
"avatar_url": "https://avatars.githubusercontent.com/u/4853075?v=4",
"profile": "https://github.com/hypnoglow",
"contributions": [
"code"
]
},
{
"login": "patricegautier",
"name": "Patrice",
"avatar_url": "https://avatars.githubusercontent.com/u/38435239?v=4",
"profile": "https://github.com/patricegautier",
"contributions": [
"code"
]
},
{
"login": "jamesmacwhite",
"name": "James White",
"avatar_url": "https://avatars.githubusercontent.com/u/8067792?v=4",
"profile": "http://jamesw.link/me",
"contributions": [
"doc"
]
},
{
"login": "Foxite",
"name": "Dirk Kok",
"avatar_url": "https://avatars.githubusercontent.com/u/20421657?v=4",
"profile": "https://ko-fi.com/foxite",
"contributions": [
"code"
]
},
{
"login": "EDIflyer",
"name": "EDIflyer",
"avatar_url": "https://avatars.githubusercontent.com/u/13610277?v=4",
"profile": "https://github.com/EDIflyer",
"contributions": [
"doc"
]
},
{
"login": "jauderho",
"name": "Jauder Ho",
"avatar_url": "https://avatars.githubusercontent.com/u/13562?v=4",
"profile": "https://github.com/jauderho",
"contributions": [
"code"
]
},
{
"login": "andriibratanin",
"name": "Andrii Bratanin",
"avatar_url": "https://avatars.githubusercontent.com/u/20169213?v=4",
"profile": "https://github.com/andriibratanin"
},
{
"login": "IAmTamal",
"name": "Tamal Das ",
"avatar_url": "https://avatars.githubusercontent.com/u/72851613?v=4",
"profile": "https://tamal.vercel.app/",
"contributions": [
"doc"
]
},
{
"login": "testwill",
"name": "guangwu",
"avatar_url": "https://avatars.githubusercontent.com/u/8717479?v=4",
"profile": "https://github.com/testwill",
"contributions": [
"doc"
]
},
{
"login": "nothub",
"name": "Florian Hübner",
"avatar_url": "https://avatars.githubusercontent.com/u/48992448?v=4",
"profile": "http://hub.lol",
"contributions": [
"doc",
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "watchtower",
"projectOwner": "containrrr",
"repoType": "github",
"repoHost": "https://github.com",
"commitConvention": "none",
"skipCi": true,
"commitType": "docs"
}
================================================
FILE: .codacy.yml
================================================
---
engines:
coverage:
exclude_paths:
- "*.md"
- "**/*.md"
================================================
FILE: .devbots/lock-issue.yml
================================================
enabled: true
comment: >
To avoid important communication to get lost in a closed issues no one monitors, I'll go ahead and lock this issue.
If you want to continue the discussion, please open a new issue. Thank you! 🙏🏼
================================================
FILE: .editorconfig
================================================
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
[*.css]
indent_style = space
indent_size = 2
[{go.mod,go.sum,*.go}]
indent_style = tab
indent_size = 4
================================================
FILE: .github/CODEOWNERS
================================================
pkg/notifications/smtp.go @piksel
pkg/notifications/email.go @piksel
pkg/notifications/shoutrrr.go @piksel @simskij @arnested
pkg/container/* @simskij
pkg/api/* @victorcmoura
.devbots/* @simskij
.github/* @simskij
docs/* @containrrr/watchtower-contributors
================================================
FILE: .github/ISSUE_TEMPLATE/bug.yml
================================================
name: 🐛 Bug report
description: Create a report to help us improve
labels: ["Priority: Medium, Status: Available, Type: Bug"]
body:
- type: markdown
attributes:
value: Before submitting your issue, please make sure you're using the containrrr/watchtower:latest image. If not, switch to this image prior to posting your report. Other forks, or the old `v2tec` image are **not** supported.
- type: textarea
id: description
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: Steps to reproduce the behavior
value: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: Please add screenshots if applicable
validations:
required: false
- type: textarea
attributes:
label: Environment
description: We would want to know the following things
value: |
- Platform
- Architecture
- Docker Version
validations:
required: true
- type: textarea
attributes:
label: Your logs
description: Paste the logs from running watchtower with the `--debug` option.
render: text
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: Add any other context about the problem here.
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://github.com/containrrr/watchtower/discussions
about: Ask questions and discuss with other community members
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: 💡 Feature request
description: Have a new idea/feature ? Please suggest!
labels: ["Priority: Low, Status: Available, Type: Enhancement"]
body:
- type: textarea
id: description
attributes:
label: Is your feature request related to a problem? Please describe.
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: solution
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
id: extrainfo
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
validations:
required: false
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "docker" # See documentation for possible values
directory: "/dockerfiles" # Location of package manifests
schedule:
interval: "weekly"
================================================
FILE: .github/pull_request_template.md
================================================
<!--
Thank you for contributing to the watchtower project! 🙏
We truly appreciate all the contributions we get from the community.
To make your PR experience as smooth as possible, make sure that you
include the following in your PR:
- What your PR contributes
- Which issues it solves (preferrably using auto closing instructions like "closes #123".
- Tests that verify the code your contributing
- Updates to the documentation
Thank you again! ✨
-->
================================================
FILE: .github/stale.yml
================================================
daysUntilStale: 60
daysUntilClose: 7
exemptMilestones: true
exemptLabels:
- "Public Service Announcement"
- "Do not close"
- "Type: Bug"
- "Type: Security"
staleLabel: "Status: Stale"
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
closeComment: false
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [main]
schedule:
- cron: '0 1 * * 4'
workflow_dispatch:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['go']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
================================================
FILE: .github/workflows/dependabot-approve.yml
================================================
name: Auto approve dependabot PRs
on: pull_request_target
jobs:
auto-approve:
runs-on: ubuntu-latest
permissions:
pull-requests: write
if: github.actor == 'dependabot[bot]'
steps:
- uses: hmarr/auto-approve-action@v3
================================================
FILE: .github/workflows/greetings.yml
================================================
name: Greetings
on:
# Runs in the context of the target (containrrr/watchtower) repository, and as such has access to GITHUB_TOKEN
pull_request_target:
types: [opened]
issues:
types: [opened]
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: >
Hi there! 👋🏼
As you're new to this repo, we'd like to suggest that you read our [code of conduct](https://github.com/containrrr/.github/blob/master/CODE_OF_CONDUCT.md)
as well as our [contribution guidelines](https://github.com/containrrr/watchtower/blob/master/CONTRIBUTING.md).
Thanks a bunch for opening your first issue! 🙏
pr-message: >
Congratulations on opening your first pull request! We'll get back to you as soon as possible. In the meantime, please make sure you've updated the documentation to reflect your changes and have added test automation as needed. Thanks! 🙏🏼
================================================
FILE: .github/workflows/publish-docs.yml
================================================
name: Publish Docs
on:
workflow_dispatch: { }
workflow_run:
workflows: [ "Release (Production)" ]
branches: [ main ]
types:
- completed
jobs:
publish-docs:
name: Publish Docs
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- name: Build tplprev
run: scripts/build-tplprev.sh
- name: Setup python
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: |
docs-requirements.txt
- name: Install mkdocs
run: |
pip install -r docs-requirements.txt
- name: Generate docs
run: mkdocs gh-deploy --strict
================================================
FILE: .github/workflows/pull-request.yml
================================================
name: Pull Request
on:
workflow_dispatch: {}
pull_request:
branches:
- main
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- uses: dominikh/staticcheck-action@ba605356b4b29a60e87ab9404b712f3461e566dc #v1.3.0
with:
version: "2023.1.6"
install-go: "false" # StaticCheck uses go v1.17 which does not support `any`
test:
name: Test
strategy:
fail-fast: false
matrix:
go-version:
- 1.20.x
platform:
- macos-latest
- windows-latest
- ubuntu-latest
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- name: Run tests
run: |
go test -v -coverprofile coverage.out -covermode atomic ./...
- name: Publish coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- name: Build
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 #v3
with:
version: v0.155.0
args: --snapshot --skip-publish --debug
================================================
FILE: .github/workflows/release-dev.yaml
================================================
name: Push to main
on:
workflow_dispatch: {}
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- name: Build
run: ./build.sh
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- name: Test
run: go test -v -coverprofile coverage.out -covermode atomic ./...
- name: Publish coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
publish:
needs:
- build
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Publish to Docker Hub
uses: jerray/publish-docker-action@87d84711629b0dc9f6bb127b568413cc92a2088e #master@2022-10-14
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
file: dockerfiles/Dockerfile.self-contained
repository: containrrr/watchtower
tags: latest-dev
- name: Publish to GHCR
uses: jerray/publish-docker-action@87d84711629b0dc9f6bb127b568413cc92a2088e #master@2022-10-14
with:
username: ${{ secrets.BOT_USERNAME }}
password: ${{ secrets.BOT_GHCR_PAT }}
file: dockerfiles/Dockerfile.self-contained
registry: ghcr.io
repository: containrrr/watchtower
tags: latest-dev
================================================
FILE: .github/workflows/release.yml
================================================
name: Release (Production)
on:
workflow_dispatch: {}
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- '**/v[0-9]+.[0-9]+.[0-9]+'
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- uses: dominikh/staticcheck-action@ba605356b4b29a60e87ab9404b712f3461e566dc #v1.3.0
with:
version: "2022.1.1"
install-go: "false" # StaticCheck uses go v1.17 which does not support `any`
test:
name: Test
strategy:
matrix:
go-version:
- 1.20.x
platform:
- ubuntu-latest
- macos-latest
- windows-latest
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- name: Run tests
run: |
go test ./... -coverprofile coverage.out
build:
name: Build
runs-on: ubuntu-latest
needs:
- test
- lint
env:
CGO_ENABLED: 0
TAG: ${{ github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
- name: Login to Docker Hub
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc #v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc #v2
with:
username: ${{ secrets.BOT_USERNAME }}
password: ${{ secrets.BOT_GHCR_PAT }}
registry: ghcr.io
- name: Build
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 #v3
with:
version: v0.155.0
args: --debug
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Enable experimental docker features
run: |
mkdir -p ~/.docker/ && \
echo '{"experimental": "enabled"}' > ~/.docker/config.json
- name: Create manifest for version
run: |
export DH_TAG=$(git tag --points-at HEAD | sed 's/^v*//')
docker manifest create \
containrrr/watchtower:$DH_TAG \
containrrr/watchtower:amd64-$DH_TAG \
containrrr/watchtower:i386-$DH_TAG \
containrrr/watchtower:armhf-$DH_TAG \
containrrr/watchtower:arm64v8-$DH_TAG
docker manifest create \
ghcr.io/containrrr/watchtower:$DH_TAG \
ghcr.io/containrrr/watchtower:amd64-$DH_TAG \
ghcr.io/containrrr/watchtower:i386-$DH_TAG \
ghcr.io/containrrr/watchtower:armhf-$DH_TAG \
ghcr.io/containrrr/watchtower:arm64v8-$DH_TAG
- name: Annotate manifest for version
run: |
for REPO in '' ghcr.io/ ; do
docker manifest annotate \
${REPO}containrrr/watchtower:$(echo $TAG | sed 's/^v*//') \
${REPO}containrrr/watchtower:i386-$(echo $TAG | sed 's/^v*//') \
--os linux \
--arch 386
docker manifest annotate \
${REPO}containrrr/watchtower:$(echo $TAG | sed 's/^v*//') \
${REPO}containrrr/watchtower:armhf-$(echo $TAG | sed 's/^v*//') \
--os linux \
--arch arm
docker manifest annotate \
${REPO}containrrr/watchtower:$(echo $TAG | sed 's/^v*//') \
${REPO}containrrr/watchtower:arm64v8-$(echo $TAG | sed 's/^v*//') \
--os linux \
--arch arm64 \
--variant v8
done
- name: Create manifest for latest
run: |
docker manifest create \
containrrr/watchtower:latest \
containrrr/watchtower:amd64-latest \
containrrr/watchtower:i386-latest \
containrrr/watchtower:armhf-latest \
containrrr/watchtower:arm64v8-latest
docker manifest create \
ghcr.io/containrrr/watchtower:latest \
ghcr.io/containrrr/watchtower:amd64-latest \
ghcr.io/containrrr/watchtower:i386-latest \
ghcr.io/containrrr/watchtower:armhf-latest \
ghcr.io/containrrr/watchtower:arm64v8-latest
- name: Annotate manifest for latest
run: |
for REPO in '' ghcr.io/ ; do
docker manifest annotate \
${REPO}containrrr/watchtower:latest \
${REPO}containrrr/watchtower:i386-latest \
--os linux \
--arch 386
docker manifest annotate \
${REPO}containrrr/watchtower:latest \
${REPO}containrrr/watchtower:armhf-latest \
--os linux \
--arch arm
docker manifest annotate \
${REPO}containrrr/watchtower:latest \
${REPO}containrrr/watchtower:arm64v8-latest \
--os linux \
--arch arm64 \
--variant v8
done
- name: Push manifests to Dockerhub
env:
DOCKER_USER: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
docker login -u $DOCKER_USER -p $DOCKER_TOKEN && \
docker manifest push containrrr/watchtower:$(echo $TAG | sed 's/^v*//') && \
docker manifest push containrrr/watchtower:latest
- name: Push manifests to GitHub Container Registry
env:
DOCKER_USER: ${{ secrets.BOT_USERNAME }}
DOCKER_TOKEN: ${{ secrets.BOT_GHCR_PAT }}
run: |
docker login -u $DOCKER_USER -p $DOCKER_TOKEN ghcr.io && \
docker manifest push ghcr.io/containrrr/watchtower:$(echo $TAG | sed 's/^v*//') && \
docker manifest push ghcr.io/containrrr/watchtower:latest
renew-docs:
name: Refresh pkg.go.dev
needs: build
runs-on: ubuntu-latest
steps:
- name: Pull new module version
uses: andrewslotin/go-proxy-pull-action@50fea06a976087614babb9508e5c528b464f4645 #master@2022-10-14
================================================
FILE: .gitignore
================================================
watchtower
watchtower.exe
vendor
.glide
dist
.idea
.DS_Store
/site
coverage.out
*.coverprofile
docs/assets/wasm_exec.js
docs/assets/*.wasm
================================================
FILE: CONTRIBUTING.md
================================================
## Prerequisites
To contribute code changes to this project you will need the following development kits.
* [Go](https://golang.org/doc/install)
* [Docker](https://docs.docker.com/engine/installation/)
As watchtower utilizes go modules for vendor locking, you'll need at least Go 1.11.
You can check your current version of the go language as follows:
```bash
~ $ go version
go version go1.12.1 darwin/amd64
```
## Checking out the code
Do not place your code in the go source path.
```bash
git clone git@github.com:<yourfork>/watchtower.git
cd watchtower
```
## Building and testing
watchtower is a go application and is built with go commands. The following commands assume that you are at the root level of your repo.
```bash
go build # compiles and packages an executable binary, watchtower
go test ./... -v # runs tests with verbose output
./watchtower # runs the application (outside of a container)
```
If you dont have it enabled, you'll either have to prefix each command with `GO111MODULE=on` or run `export GO111MODULE=on` before running the commands. [You can read more about modules here.](https://github.com/golang/go/wiki/Modules)
To build a Watchtower image of your own, use the self-contained Dockerfiles. As the main Dockerfile, they can be found in `dockerfiles/`:
- `dockerfiles/Dockerfile.dev-self-contained` will build an image based on your current local Watchtower files.
- `dockerfiles/Dockerfile.self-contained` will build an image based on current Watchtower's repository on GitHub.
e.g.:
```bash
sudo docker build . -f dockerfiles/Dockerfile.dev-self-contained -t containrrr/watchtower # to build an image from local files
```
================================================
FILE: LICENSE.md
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Watchtower contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Watchtower contains code that is licensed under a BSD-license:
- Copyright (c) 2009 The Go Authors. All rights reserved.
For details see https://golang.org/LICENSE
================================================
FILE: README.md
================================================
<div align="center">
### ⚠️ This project is no longer maintained
See https://github.com/containrrr/watchtower/discussions/2135 for details.
---
<img src="./logo.png" width="450" />
# Watchtower
A process for automating Docker container base image updates.
<br/><br/>
[](https://circleci.com/gh/containrrr/watchtower)
[](https://codecov.io/gh/containrrr/watchtower)
[](https://godoc.org/github.com/containrrr/watchtower)
[](https://goreportcard.com/report/github.com/containrrr/watchtower)
[](https://github.com/containrrr/watchtower/releases)
[](https://www.apache.org/licenses/LICENSE-2.0)
[](https://www.codacy.com/gh/containrrr/watchtower/dashboard?utm_source=github.com&utm_medium=referral&utm_content=containrrr/watchtower&utm_campaign=Badge_Grade)
[](#contributors)
[](https://hub.docker.com/r/containrrr/watchtower)
</div>
## Quick Start
With watchtower you can update the running version of your containerized app simply by pushing a new image to the Docker Hub or your own image registry.
Watchtower will pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially. Run the watchtower container with the following command:
```
$ docker run --detach \
--name watchtower \
--volume /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower
```
Watchtower is intended to be used in homelabs, media centers, local dev environments, and similar. We do **not** recommend using Watchtower in a commercial or production environment. If that is you, you should be looking into using Kubernetes. If that feels like too big a step for you, please look into solutions like [MicroK8s](https://microk8s.io/) and [k3s](https://k3s.io/) that take away a lot of the toil of running a Kubernetes cluster.
## Documentation
The full documentation is available at https://containrrr.dev/watchtower.
## Contributors
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://piksel.se"><img src="https://avatars2.githubusercontent.com/u/807383?v=4?s=100" width="100px;" alt="nils måsén"/><br /><sub><b>nils måsén</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=piksel" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=piksel" title="Documentation">📖</a> <a href="#maintenance-piksel" title="Maintenance">🚧</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Apiksel" title="Reviewed Pull Requests">👀</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://simme.dev"><img src="https://avatars0.githubusercontent.com/u/1596025?v=4?s=100" width="100px;" alt="Simon Aronsson"/><br /><sub><b>Simon Aronsson</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=simskij" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=simskij" title="Documentation">📖</a> <a href="#maintenance-simskij" title="Maintenance">🚧</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Asimskij" title="Reviewed Pull Requests">👀</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://codelica.com"><img src="https://avatars3.githubusercontent.com/u/386101?v=4?s=100" width="100px;" alt="James"/><br /><sub><b>James</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Codelica" title="Tests">⚠️</a> <a href="#ideas-Codelica" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://kopfkrieg.org"><img src="https://avatars2.githubusercontent.com/u/5047813?v=4?s=100" width="100px;" alt="Florian"/><br /><sub><b>Florian</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3AKopfKrieg" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/containrrr/watchtower/commits?author=KopfKrieg" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bdehamer"><img src="https://avatars1.githubusercontent.com/u/398027?v=4?s=100" width="100px;" alt="Brian DeHamer"/><br /><sub><b>Brian DeHamer</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=bdehamer" title="Code">💻</a> <a href="#maintenance-bdehamer" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rosscado"><img src="https://avatars1.githubusercontent.com/u/16578183?v=4?s=100" width="100px;" alt="Ross Cadogan"/><br /><sub><b>Ross Cadogan</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rosscado" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/stffabi"><img src="https://avatars0.githubusercontent.com/u/9464631?v=4?s=100" width="100px;" alt="stffabi"/><br /><sub><b>stffabi</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=stffabi" title="Code">💻</a> <a href="#maintenance-stffabi" title="Maintenance">🚧</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ATCUSA"><img src="https://avatars3.githubusercontent.com/u/3581228?v=4?s=100" width="100px;" alt="Austin"/><br /><sub><b>Austin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ATCUSA" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://labs.ctl.io"><img src="https://avatars2.githubusercontent.com/u/6181487?v=4?s=100" width="100px;" alt="David Gardner"/><br /><sub><b>David Gardner</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Adavidgardner11" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/containrrr/watchtower/commits?author=davidgardner11" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dolanor"><img src="https://avatars3.githubusercontent.com/u/928722?v=4?s=100" width="100px;" alt="Tanguy ⧓ Herrmann"/><br /><sub><b>Tanguy ⧓ Herrmann</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=dolanor" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rdamazio"><img src="https://avatars3.githubusercontent.com/u/997641?v=4?s=100" width="100px;" alt="Rodrigo Damazio Bovendorp"/><br /><sub><b>Rodrigo Damazio Bovendorp</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rdamazio" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=rdamazio" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.taisun.io/"><img src="https://avatars3.githubusercontent.com/u/1852688?v=4?s=100" width="100px;" alt="Ryan Kuba"/><br /><sub><b>Ryan Kuba</b></sub></a><br /><a href="#infra-thelamer" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cnrmck"><img src="https://avatars2.githubusercontent.com/u/22061955?v=4?s=100" width="100px;" alt="cnrmck"/><br /><sub><b>cnrmck</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=cnrmck" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://harrywalter.co.uk"><img src="https://avatars3.githubusercontent.com/u/338588?v=4?s=100" width="100px;" alt="Harry Walter"/><br /><sub><b>Harry Walter</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=haswalt" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://projectsperanza.com"><img src="https://avatars3.githubusercontent.com/u/74515?v=4?s=100" width="100px;" alt="Robotex"/><br /><sub><b>Robotex</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Robotex" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://geraldpape.io"><img src="https://avatars0.githubusercontent.com/u/1494211?v=4?s=100" width="100px;" alt="Gerald Pape"/><br /><sub><b>Gerald Pape</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ubergesundheit" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fomk"><img src="https://avatars0.githubusercontent.com/u/17636183?v=4?s=100" width="100px;" alt="fomk"/><br /><sub><b>fomk</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=fomk" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/svengo"><img src="https://avatars3.githubusercontent.com/u/2502366?v=4?s=100" width="100px;" alt="Sven Gottwald"/><br /><sub><b>Sven Gottwald</b></sub></a><br /><a href="#infra-svengo" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://liberapay.com/techknowlogick/"><img src="https://avatars1.githubusercontent.com/u/164197?v=4?s=100" width="100px;" alt="techknowlogick"/><br /><sub><b>techknowlogick</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=techknowlogick" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://log.c5t.org/about/"><img src="https://avatars1.githubusercontent.com/u/1449568?v=4?s=100" width="100px;" alt="waja"/><br /><sub><b>waja</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=waja" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://scottalbertson.com"><img src="https://avatars2.githubusercontent.com/u/154463?v=4?s=100" width="100px;" alt="Scott Albertson"/><br /><sub><b>Scott Albertson</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=salbertson" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/huddlesj"><img src="https://avatars1.githubusercontent.com/u/11966535?v=4?s=100" width="100px;" alt="Jason Huddleston"/><br /><sub><b>Jason Huddleston</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=huddlesj" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://npstr.space/"><img src="https://avatars3.githubusercontent.com/u/6048348?v=4?s=100" width="100px;" alt="Napster"/><br /><sub><b>Napster</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=napstr" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/darknode"><img src="https://avatars1.githubusercontent.com/u/809429?v=4?s=100" width="100px;" alt="Maxim"/><br /><sub><b>Maxim</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=darknode" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=darknode" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://schmitt.cat"><img src="https://avatars0.githubusercontent.com/u/17984549?v=4?s=100" width="100px;" alt="Max Schmitt"/><br /><sub><b>Max Schmitt</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mxschmitt" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cron410"><img src="https://avatars1.githubusercontent.com/u/3082899?v=4?s=100" width="100px;" alt="cron410"/><br /><sub><b>cron410</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=cron410" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cardoso222"><img src="https://avatars3.githubusercontent.com/u/7026517?v=4?s=100" width="100px;" alt="Paulo Henrique"/><br /><sub><b>Paulo Henrique</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Cardoso222" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://coded.io"><img src="https://avatars0.githubusercontent.com/u/107097?v=4?s=100" width="100px;" alt="Kaleb Elwert"/><br /><sub><b>Kaleb Elwert</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=belak" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wmbutler"><img src="https://avatars1.githubusercontent.com/u/1254810?v=4?s=100" width="100px;" alt="Bill Butler"/><br /><sub><b>Bill Butler</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=wmbutler" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.mariotacke.io"><img src="https://avatars2.githubusercontent.com/u/4942019?v=4?s=100" width="100px;" alt="Mario Tacke"/><br /><sub><b>Mario Tacke</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mariotacke" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://markwoodbridge.com"><img src="https://avatars2.githubusercontent.com/u/1101318?v=4?s=100" width="100px;" alt="Mark Woodbridge"/><br /><sub><b>Mark Woodbridge</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mrw34" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ansem93"><img src="https://avatars3.githubusercontent.com/u/6626218?v=4?s=100" width="100px;" alt="Ansem93"/><br /><sub><b>Ansem93</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Ansem93" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lukapeschke"><img src="https://avatars1.githubusercontent.com/u/17085536?v=4?s=100" width="100px;" alt="Luka Peschke"/><br /><sub><b>Luka Peschke</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=lukapeschke" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=lukapeschke" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zoispag"><img src="https://avatars0.githubusercontent.com/u/21138205?v=4?s=100" width="100px;" alt="Zois Pagoulatos"/><br /><sub><b>Zois Pagoulatos</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=zoispag" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Azoispag" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-zoispag" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://alexandre.menif.name"><img src="https://avatars0.githubusercontent.com/u/16152103?v=4?s=100" width="100px;" alt="Alexandre Menif"/><br /><sub><b>Alexandre Menif</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=alexandremenif" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chugunov"><img src="https://avatars1.githubusercontent.com/u/4140479?v=4?s=100" width="100px;" alt="Andrey"/><br /><sub><b>Andrey</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=chugunov" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://noplanman.ch"><img src="https://avatars3.githubusercontent.com/u/9423417?v=4?s=100" width="100px;" alt="Armando Lüscher"/><br /><sub><b>Armando Lüscher</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=noplanman" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rjbudke"><img src="https://avatars2.githubusercontent.com/u/273485?v=4?s=100" width="100px;" alt="Ryan Budke"/><br /><sub><b>Ryan Budke</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rjbudke" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://kaloyan.raev.name"><img src="https://avatars2.githubusercontent.com/u/468091?v=4?s=100" width="100px;" alt="Kaloyan Raev"/><br /><sub><b>Kaloyan Raev</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=kaloyan-raev" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=kaloyan-raev" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sixth"><img src="https://avatars3.githubusercontent.com/u/11591445?v=4?s=100" width="100px;" alt="sixth"/><br /><sub><b>sixth</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=sixth" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://foosel.net"><img src="https://avatars0.githubusercontent.com/u/83657?v=4?s=100" width="100px;" alt="Gina Häußge"/><br /><sub><b>Gina Häußge</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=foosel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/8ear"><img src="https://avatars0.githubusercontent.com/u/10329648?v=4?s=100" width="100px;" alt="Max H."/><br /><sub><b>Max H.</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=8ear" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://pjknkda.github.io"><img src="https://avatars0.githubusercontent.com/u/4986524?v=4?s=100" width="100px;" alt="Jungkook Park"/><br /><sub><b>Jungkook Park</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=pjknkda" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://achfrag.net"><img src="https://avatars1.githubusercontent.com/u/5753622?v=4?s=100" width="100px;" alt="Jan Kristof Nidzwetzki"/><br /><sub><b>Jan Kristof Nidzwetzki</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jnidzwetzki" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.lukaselsner.de"><img src="https://avatars0.githubusercontent.com/u/1413542?v=4?s=100" width="100px;" alt="lukas"/><br /><sub><b>lukas</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mindrunner" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://codingcoffee.dev"><img src="https://avatars3.githubusercontent.com/u/13611153?v=4?s=100" width="100px;" alt="Ameya Shenoy"/><br /><sub><b>Ameya Shenoy</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=codingCoffee" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/raymondelooff"><img src="https://avatars0.githubusercontent.com/u/9716806?v=4?s=100" width="100px;" alt="Raymon de Looff"/><br /><sub><b>Raymon de Looff</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=raymondelooff" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://codemonkeylabs.com"><img src="https://avatars2.githubusercontent.com/u/704034?v=4?s=100" width="100px;" alt="John Clayton"/><br /><sub><b>John Clayton</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jsclayton" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Germs2004"><img src="https://avatars2.githubusercontent.com/u/5519340?v=4?s=100" width="100px;" alt="Germs2004"/><br /><sub><b>Germs2004</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Germs2004" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lukwil"><img src="https://avatars1.githubusercontent.com/u/30203234?v=4?s=100" width="100px;" alt="Lukas Willburger"/><br /><sub><b>Lukas Willburger</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=lukwil" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/auanasgheps"><img src="https://avatars2.githubusercontent.com/u/20586878?v=4?s=100" width="100px;" alt="Oliver Cervera"/><br /><sub><b>Oliver Cervera</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=auanasgheps" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/victorcmoura"><img src="https://avatars1.githubusercontent.com/u/26290053?v=4?s=100" width="100px;" alt="Victor Moura"/><br /><sub><b>Victor Moura</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Tests">⚠️</a> <a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mbrandau"><img src="https://avatars3.githubusercontent.com/u/12972798?v=4?s=100" width="100px;" alt="Maximilian Brandau"/><br /><sub><b>Maximilian Brandau</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mbrandau" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=mbrandau" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aneisch"><img src="https://avatars1.githubusercontent.com/u/6991461?v=4?s=100" width="100px;" alt="Andrew"/><br /><sub><b>Andrew</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=aneisch" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sixcorners"><img src="https://avatars0.githubusercontent.com/u/585501?v=4?s=100" width="100px;" alt="sixcorners"/><br /><sub><b>sixcorners</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=sixcorners" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://arnested.dk"><img src="https://avatars2.githubusercontent.com/u/190005?v=4?s=100" width="100px;" alt="Arne Jørgensen"/><br /><sub><b>Arne Jørgensen</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=arnested" title="Tests">⚠️</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Aarnested" title="Reviewed Pull Requests">👀</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/patski123"><img src="https://avatars1.githubusercontent.com/u/19295295?v=4?s=100" width="100px;" alt="PatSki123"/><br /><sub><b>PatSki123</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=patski123" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://rubyroidlabs.com/"><img src="https://avatars2.githubusercontent.com/u/624999?v=4?s=100" width="100px;" alt="Valentine Zavadsky"/><br /><sub><b>Valentine Zavadsky</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Saicheg" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=Saicheg" title="Documentation">📖</a> <a href="https://github.com/containrrr/watchtower/commits?author=Saicheg" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bopoh24"><img src="https://avatars2.githubusercontent.com/u/4086631?v=4?s=100" width="100px;" alt="Alexander Voronin"/><br /><sub><b>Alexander Voronin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=bopoh24" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/issues?q=author%3Abopoh24" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.teqneers.de"><img src="https://avatars0.githubusercontent.com/u/788989?v=4?s=100" width="100px;" alt="Oliver Mueller"/><br /><sub><b>Oliver Mueller</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ogmueller" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tammert"><img src="https://avatars0.githubusercontent.com/u/8885250?v=4?s=100" width="100px;" alt="Sebastiaan Tammer"/><br /><sub><b>Sebastiaan Tammer</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=tammert" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Miosame"><img src="https://avatars1.githubusercontent.com/u/8201077?v=4?s=100" width="100px;" alt="miosame"/><br /><sub><b>miosame</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=miosame" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://mtz.gr"><img src="https://avatars3.githubusercontent.com/u/590246?v=4?s=100" width="100px;" alt="Andrew Metzger"/><br /><sub><b>Andrew Metzger</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/issues?q=author%3Aandrewjmetzger" title="Bug reports">🐛</a> <a href="#example-andrewjmetzger" title="Examples">💡</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pgrimaud"><img src="https://avatars1.githubusercontent.com/u/1866496?v=4?s=100" width="100px;" alt="Pierre Grimaud"/><br /><sub><b>Pierre Grimaud</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=pgrimaud" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mattdoran"><img src="https://avatars0.githubusercontent.com/u/577779?v=4?s=100" width="100px;" alt="Matt Doran"/><br /><sub><b>Matt Doran</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mattdoran" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MihailITPlace"><img src="https://avatars2.githubusercontent.com/u/28401551?v=4?s=100" width="100px;" alt="MihailITPlace"/><br /><sub><b>MihailITPlace</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=MihailITPlace" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bugficks"><img src="https://avatars1.githubusercontent.com/u/2992895?v=4?s=100" width="100px;" alt="bugficks"/><br /><sub><b>bugficks</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=bugficks" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=bugficks" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MichaelSp"><img src="https://avatars0.githubusercontent.com/u/448282?v=4?s=100" width="100px;" alt="Michael"/><br /><sub><b>Michael</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=MichaelSp" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jokay"><img src="https://avatars0.githubusercontent.com/u/18613935?v=4?s=100" width="100px;" alt="D. Domig"/><br /><sub><b>D. Domig</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jokay" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://maxwells-daemon.io"><img src="https://avatars1.githubusercontent.com/u/260084?v=4?s=100" width="100px;" alt="Ben Osheroff"/><br /><sub><b>Ben Osheroff</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=osheroff" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dhet"><img src="https://avatars3.githubusercontent.com/u/2668621?v=4?s=100" width="100px;" alt="David H."/><br /><sub><b>David H.</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=dhet" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.gridgeo.com"><img src="https://avatars1.githubusercontent.com/u/671887?v=4?s=100" width="100px;" alt="Chander Ganesan"/><br /><sub><b>Chander Ganesan</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=chander" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yrien30"><img src="https://avatars1.githubusercontent.com/u/26816162?v=4?s=100" width="100px;" alt="yrien30"/><br /><sub><b>yrien30</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=yrien30" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ksurl"><img src="https://avatars1.githubusercontent.com/u/1371562?v=4?s=100" width="100px;" alt="ksurl"/><br /><sub><b>ksurl</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ksurl" title="Documentation">📖</a> <a href="https://github.com/containrrr/watchtower/commits?author=ksurl" title="Code">💻</a> <a href="#infra-ksurl" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rg9400"><img src="https://avatars2.githubusercontent.com/u/39887349?v=4?s=100" width="100px;" alt="rg9400"/><br /><sub><b>rg9400</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rg9400" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tkalus"><img src="https://avatars2.githubusercontent.com/u/287181?v=4?s=100" width="100px;" alt="Turtle Kalus"/><br /><sub><b>Turtle Kalus</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=tkalus" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SrihariThalla"><img src="https://avatars1.githubusercontent.com/u/7479937?v=4?s=100" width="100px;" alt="Srihari Thalla"/><br /><sub><b>Srihari Thalla</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=SrihariThalla" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://nymous.io"><img src="https://avatars1.githubusercontent.com/u/4216559?v=4?s=100" width="100px;" alt="Thomas Gaudin"/><br /><sub><b>Thomas Gaudin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=nymous" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://indigo.re/"><img src="https://avatars.githubusercontent.com/u/2804645?v=4?s=100" width="100px;" alt="hydrargyrum"/><br /><sub><b>hydrargyrum</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=hydrargyrum" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://reinout.vanrees.org"><img src="https://avatars.githubusercontent.com/u/121433?v=4?s=100" width="100px;" alt="Reinout van Rees"/><br /><sub><b>Reinout van Rees</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=reinout" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DasSkelett"><img src="https://avatars.githubusercontent.com/u/28812678?v=4?s=100" width="100px;" alt="DasSkelett"/><br /><sub><b>DasSkelett</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=DasSkelett" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zenjabba"><img src="https://avatars.githubusercontent.com/u/679864?v=4?s=100" width="100px;" alt="zenjabba"/><br /><sub><b>zenjabba</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=zenjabba" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://quan.io"><img src="https://avatars.githubusercontent.com/u/3526705?v=4?s=100" width="100px;" alt="Dan Quan"/><br /><sub><b>Dan Quan</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=djquan" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/modem7"><img src="https://avatars.githubusercontent.com/u/4349962?v=4?s=100" width="100px;" alt="modem7"/><br /><sub><b>modem7</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=modem7" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hypnoglow"><img src="https://avatars.githubusercontent.com/u/4853075?v=4?s=100" width="100px;" alt="Igor Zibarev"/><br /><sub><b>Igor Zibarev</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=hypnoglow" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/patricegautier"><img src="https://avatars.githubusercontent.com/u/38435239?v=4?s=100" width="100px;" alt="Patrice"/><br /><sub><b>Patrice</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=patricegautier" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://jamesw.link/me"><img src="https://avatars.githubusercontent.com/u/8067792?v=4?s=100" width="100px;" alt="James White"/><br /><sub><b>James White</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jamesmacwhite" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ko-fi.com/foxite"><img src="https://avatars.githubusercontent.com/u/20421657?v=4?s=100" width="100px;" alt="Dirk Kok"/><br /><sub><b>Dirk Kok</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Foxite" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EDIflyer"><img src="https://avatars.githubusercontent.com/u/13610277?v=4?s=100" width="100px;" alt="EDIflyer"/><br /><sub><b>EDIflyer</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=EDIflyer" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jauderho"><img src="https://avatars.githubusercontent.com/u/13562?v=4?s=100" width="100px;" alt="Jauder Ho"/><br /><sub><b>Jauder Ho</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jauderho" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://tamal.vercel.app/"><img src="https://avatars.githubusercontent.com/u/72851613?v=4?s=100" width="100px;" alt="Tamal Das "/><br /><sub><b>Tamal Das </b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=IAmTamal" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/testwill"><img src="https://avatars.githubusercontent.com/u/8717479?v=4?s=100" width="100px;" alt="guangwu"/><br /><sub><b>guangwu</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=testwill" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hub.lol"><img src="https://avatars.githubusercontent.com/u/48992448?v=4?s=100" width="100px;" alt="Florian Hübner"/><br /><sub><b>Florian Hübner</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=nothub" title="Documentation">📖</a> <a href="https://github.com/containrrr/watchtower/commits?author=nothub" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/andriibratanin"><img src="https://avatars.githubusercontent.com/u/20169213?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrii Bratanin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=andriibratanin" title="Documentation">📖</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
Security updates will always only be applied to the latest version of Watchtower.
As the software by default is set to auto-update if you use the `latest` tag, you will get these security updates automatically as soon as they are released.
## Reporting a Vulnerability
Critical vulnerabilities that might open up for external attacks are best reported directly either to simme@arcticbit.se or nils@piksel.se.
We'll always try to get back to you as swiftly as possible, but keep in mind that since this is a community project, we can't really leave any guarantees about the speed.
Non-critical vulnerabilities may be reported as regular GitHub issues.
================================================
FILE: build.sh
================================================
#!/bin/bash
BINFILE=watchtower
if [ -n "$MSYSTEM" ]; then
BINFILE=watchtower.exe
fi
VERSION=$(git describe --tags)
echo "Building $VERSION..."
go build -o $BINFILE -ldflags "-X github.com/containrrr/watchtower/internal/meta.Version=$VERSION"
================================================
FILE: cmd/notify-upgrade.go
================================================
// Package cmd contains the watchtower (sub-)commands
package cmd
import (
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/containrrr/watchtower/internal/flags"
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/notifications"
"github.com/spf13/cobra"
)
var notifyUpgradeCommand = NewNotifyUpgradeCommand()
// NewNotifyUpgradeCommand creates the notify upgrade command for watchtower
func NewNotifyUpgradeCommand() *cobra.Command {
return &cobra.Command{
Use: "notify-upgrade",
Short: "Upgrade legacy notification configuration to shoutrrr URLs",
Run: runNotifyUpgrade,
}
}
func runNotifyUpgrade(cmd *cobra.Command, args []string) {
if err := runNotifyUpgradeE(cmd, args); err != nil {
logf("Notification upgrade failed: %v", err)
}
}
func runNotifyUpgradeE(cmd *cobra.Command, _ []string) error {
f := cmd.Flags()
flags.ProcessFlagAliases(f)
notifier = notifications.NewNotifier(cmd)
urls := notifier.GetURLs()
logf("Found notification configurations for: %v", strings.Join(notifier.GetNames(), ", "))
outFile, err := os.CreateTemp("/", "watchtower-notif-urls-*")
if err != nil {
return fmt.Errorf("failed to create output file: %v", err)
}
logf("Writing notification URLs to %v", outFile.Name())
logf("")
sb := strings.Builder{}
sb.WriteString("WATCHTOWER_NOTIFICATION_URL=")
for i, u := range urls {
if i != 0 {
sb.WriteRune(' ')
}
sb.WriteString(u)
}
_, err = fmt.Fprint(outFile, sb.String())
tryOrLog(err, "Failed to write to output file")
tryOrLog(outFile.Sync(), "Failed to sync output file")
tryOrLog(outFile.Close(), "Failed to close output file")
containerID := "<CONTAINER>"
cid, err := container.GetRunningContainerID()
tryOrLog(err, "Failed to get running container ID")
if cid != "" {
containerID = cid.ShortID()
}
logf("To get the environment file, use:")
logf("cp %v:%v ./watchtower-notifications.env", containerID, outFile.Name())
logf("")
logf("Note: This file will be removed in 5 minutes or when this container is stopped!")
signalChannel := make(chan os.Signal, 1)
time.AfterFunc(5*time.Minute, func() {
signalChannel <- syscall.SIGALRM
})
signal.Notify(signalChannel, os.Interrupt)
signal.Notify(signalChannel, syscall.SIGTERM)
switch <-signalChannel {
case syscall.SIGALRM:
logf("Timed out!")
case os.Interrupt, syscall.SIGTERM:
logf("Stopping...")
default:
}
if err := os.Remove(outFile.Name()); err != nil {
logf("Failed to remove file, it may still be present in the container image! Error: %v", err)
} else {
logf("Environment file has been removed.")
}
return nil
}
func tryOrLog(err error, message string) {
if err != nil {
logf("%v: %v\n", message, err)
}
}
func logf(format string, v ...interface{}) {
fmt.Fprintln(os.Stderr, fmt.Sprintf(format, v...))
}
================================================
FILE: cmd/root.go
================================================
package cmd
import (
"errors"
"math"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"github.com/containrrr/watchtower/internal/actions"
"github.com/containrrr/watchtower/internal/flags"
"github.com/containrrr/watchtower/internal/meta"
"github.com/containrrr/watchtower/pkg/api"
apiMetrics "github.com/containrrr/watchtower/pkg/api/metrics"
"github.com/containrrr/watchtower/pkg/api/update"
"github.com/containrrr/watchtower/pkg/container"
"github.com/containrrr/watchtower/pkg/filters"
"github.com/containrrr/watchtower/pkg/metrics"
"github.com/containrrr/watchtower/pkg/notifications"
t "github.com/containrrr/watchtower/pkg/types"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
client container.Client
scheduleSpec string
cleanup bool
noRestart bool
noPull bool
monitorOnly bool
enableLabel bool
disableContainers []string
notifier t.Notifier
timeout time.Duration
lifecycleHooks bool
rollingRestart bool
scope string
labelPrecedence bool
)
var rootCmd = NewRootCommand()
// NewRootCommand creates the root command for watchtower
func NewRootCommand() *cobra.Command {
return &cobra.Command{
Use: "watchtower",
Short: "Automatically updates running Docker containers",
Long: `
Watchtower automatically updates running Docker containers whenever a new image is released.
More information available at https://github.com/containrrr/watchtower/.
`,
Run: Run,
PreRun: PreRun,
Args: cobra.ArbitraryArgs,
}
}
func init() {
flags.SetDefaults()
flags.RegisterDockerFlags(rootCmd)
flags.RegisterSystemFlags(rootCmd)
flags.RegisterNotificationFlags(rootCmd)
}
// Execute the root func and exit in case of errors
func Execute() {
rootCmd.AddCommand(notifyUpgradeCommand)
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}
// PreRun is a lifecycle hook that runs before the command is executed.
func PreRun(cmd *cobra.Command, _ []string) {
f := cmd.PersistentFlags()
flags.ProcessFlagAliases(f)
if err := flags.SetupLogging(f); err != nil {
log.Fatalf("Failed to initialize logging: %s", err.Error())
}
scheduleSpec, _ = f.GetString("schedule")
flags.GetSecretsFromFiles(cmd)
cleanup, noRestart, monitorOnly, timeout = flags.ReadFlags(cmd)
if timeout < 0 {
log.Fatal("Please specify a positive value for timeout value.")
}
enableLabel, _ = f.GetBool("label-enable")
disableContainers, _ = f.GetStringSlice("disable-containers")
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
rollingRestart, _ = f.GetBool("rolling-restart")
scope, _ = f.GetString("scope")
labelPrecedence, _ = f.GetBool("label-take-precedence")
if scope != "" {
log.Debugf(`Using scope %q`, scope)
}
// configure environment vars for client
err := flags.EnvConfig(cmd)
if err != nil {
log.Fatal(err)
}
noPull, _ = f.GetBool("no-pull")
includeStopped, _ := f.GetBool("include-stopped")
includeRestarting, _ := f.GetBool("include-restarting")
reviveStopped, _ := f.GetBool("revive-stopped")
removeVolumes, _ := f.GetBool("remove-volumes")
warnOnHeadPullFailed, _ := f.GetString("warn-on-head-failure")
if monitorOnly && noPull {
log.Warn("Using `WATCHTOWER_NO_PULL` and `WATCHTOWER_MONITOR_ONLY` simultaneously might lead to no action being taken at all. If this is intentional, you may safely ignore this message.")
}
client = container.NewClient(container.ClientOptions{
IncludeStopped: includeStopped,
ReviveStopped: reviveStopped,
RemoveVolumes: removeVolumes,
IncludeRestarting: includeRestarting,
WarnOnHeadFailed: container.WarningStrategy(warnOnHeadPullFailed),
})
notifier = notifications.NewNotifier(cmd)
notifier.AddLogHook()
}
// Run is the main execution flow of the command
func Run(c *cobra.Command, names []string) {
filter, filterDesc := filters.BuildFilter(names, disableContainers, enableLabel, scope)
runOnce, _ := c.PersistentFlags().GetBool("run-once")
enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update")
enableMetricsAPI, _ := c.PersistentFlags().GetBool("http-api-metrics")
unblockHTTPAPI, _ := c.PersistentFlags().GetBool("http-api-periodic-polls")
apiToken, _ := c.PersistentFlags().GetString("http-api-token")
healthCheck, _ := c.PersistentFlags().GetBool("health-check")
if healthCheck {
// health check should not have pid 1
if os.Getpid() == 1 {
time.Sleep(1 * time.Second)
log.Fatal("The health check flag should never be passed to the main watchtower container process")
}
os.Exit(0)
}
if rollingRestart && monitorOnly {
log.Fatal("Rolling restarts is not compatible with the global monitor only flag")
}
awaitDockerClient()
if err := actions.CheckForSanity(client, filter, rollingRestart); err != nil {
logNotifyExit(err)
}
if runOnce {
writeStartupMessage(c, time.Time{}, filterDesc)
runUpdatesWithNotifications(filter)
notifier.Close()
os.Exit(0)
return
}
if err := actions.CheckForMultipleWatchtowerInstances(client, cleanup, scope); err != nil {
logNotifyExit(err)
}
// The lock is shared between the scheduler and the HTTP API. It only allows one update to run at a time.
updateLock := make(chan bool, 1)
updateLock <- true
httpAPI := api.New(apiToken)
if enableUpdateAPI {
updateHandler := update.New(func(images []string) {
metric := runUpdatesWithNotifications(filters.FilterByImage(images, filter))
metrics.RegisterScan(metric)
}, updateLock)
httpAPI.RegisterFunc(updateHandler.Path, updateHandler.Handle)
// If polling isn't enabled the scheduler is never started, and
// we need to trigger the startup messages manually.
if !unblockHTTPAPI {
writeStartupMessage(c, time.Time{}, filterDesc)
}
}
if enableMetricsAPI {
metricsHandler := apiMetrics.New()
httpAPI.RegisterHandler(metricsHandler.Path, metricsHandler.Handle)
}
if err := httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Error("failed to start API", err)
}
if err := runUpgradesOnSchedule(c, filter, filterDesc, updateLock); err != nil {
log.Error(err)
}
os.Exit(1)
}
func logNotifyExit(err error) {
log.Error(err)
notifier.Close()
os.Exit(1)
}
func awaitDockerClient() {
log.Debug("Sleeping for a second to ensure the docker api client has been properly initialized.")
time.Sleep(1 * time.Second)
}
func formatDuration(d time.Duration) string {
sb := strings.Builder{}
hours := int64(d.Hours())
minutes := int64(math.Mod(d.Minutes(), 60))
seconds := int64(math.Mod(d.Seconds(), 60))
if hours == 1 {
sb.WriteString("1 hour")
} else if hours != 0 {
sb.WriteString(strconv.FormatInt(hours, 10))
sb.WriteString(" hours")
}
if hours != 0 && (seconds != 0 || minutes != 0) {
sb.WriteString(", ")
}
if minutes == 1 {
sb.WriteString("1 minute")
} else if minutes != 0 {
sb.WriteString(strconv.FormatInt(minutes, 10))
sb.WriteString(" minutes")
}
if minutes != 0 && (seconds != 0) {
sb.WriteString(", ")
}
if seconds == 1 {
sb.WriteString("1 second")
} else if seconds != 0 || (hours == 0 && minutes == 0) {
sb.WriteString(strconv.FormatInt(seconds, 10))
sb.WriteString(" seconds")
}
return sb.String()
}
func writeStartupMessage(c *cobra.Command, sched time.Time, filtering string) {
noStartupMessage, _ := c.PersistentFlags().GetBool("no-startup-message")
enableUpdateAPI, _ := c.PersistentFlags().GetBool("http-api-update")
var startupLog *log.Entry
if noStartupMessage {
startupLog = notifications.LocalLog
} else {
startupLog = log.NewEntry(log.StandardLogger())
// Batch up startup messages to send them as a single notification
notifier.StartNotification()
}
startupLog.Info("Watchtower ", meta.Version)
notifierNames := notifier.GetNames()
if len(notifierNames) > 0 {
startupLog.Info("Using notifications: " + strings.Join(notifierNames, ", "))
} else {
startupLog.Info("Using no notifications")
}
startupLog.Info(filtering)
if !sched.IsZero() {
until := formatDuration(time.Until(sched))
startupLog.Info("Scheduling first run: " + sched.Format("2006-01-02 15:04:05 -0700 MST"))
startupLog.Info("Note that the first check will be performed in " + until)
} else if runOnce, _ := c.PersistentFlags().GetBool("run-once"); runOnce {
startupLog.Info("Running a one time update.")
} else {
startupLog.Info("Periodic runs are not enabled.")
}
if enableUpdateAPI {
// TODO: make listen port configurable
startupLog.Info("The HTTP API is enabled at :8080.")
}
if !noStartupMessage {
// Send the queued up startup messages, not including the trace warning below (to make sure it's noticed)
notifier.SendNotification(nil)
}
if log.IsLevelEnabled(log.TraceLevel) {
startupLog.Warn("Trace level enabled: log will include sensitive information as credentials and tokens")
}
}
func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering string, lock chan bool) error {
if lock == nil {
lock = make(chan bool, 1)
lock <- true
}
scheduler := cron.New()
err := scheduler.AddFunc(
scheduleSpec,
func() {
select {
case v := <-lock:
defer func() { lock <- v }()
metric := runUpdatesWithNotifications(filter)
metrics.RegisterScan(metric)
default:
// Update was skipped
metrics.RegisterScan(nil)
log.Debug("Skipped another update already running.")
}
nextRuns := scheduler.Entries()
if len(nextRuns) > 0 {
log.Debug("Scheduled next run: " + nextRuns[0].Next.String())
}
})
if err != nil {
return err
}
writeStartupMessage(c, scheduler.Entries()[0].Schedule.Next(time.Now()), filtering)
scheduler.Start()
// Graceful shut-down on SIGINT/SIGTERM
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
signal.Notify(interrupt, syscall.SIGTERM)
<-interrupt
scheduler.Stop()
log.Info("Waiting for running update to be finished...")
<-lock
return nil
}
func runUpdatesWithNotifications(filter t.Filter) *metrics.Metric {
notifier.StartNotification()
updateParams := t.UpdateParams{
Filter: filter,
Cleanup: cleanup,
NoRestart: noRestart,
Timeout: timeout,
MonitorOnly: monitorOnly,
LifecycleHooks: lifecycleHooks,
RollingRestart: rollingRestart,
LabelPrecedence: labelPrecedence,
NoPull: noPull,
}
result, err := actions.Update(client, updateParams)
if err != nil {
log.Error(err)
}
notifier.SendNotification(result)
metricResults := metrics.NewMetric(result)
notifications.LocalLog.WithFields(log.Fields{
"Scanned": metricResults.Scanned,
"Updated": metricResults.Updated,
"Failed": metricResults.Failed,
}).Info("Session done")
return metricResults
}
================================================
FILE: code_of_conduct.md
================================================
### Containrrr Community Code of Conduct
Please refer to out [Containrrr Community Code of Conduct](https://github.com/containrrr/.github/blob/master/CODE_OF_CONDUCT.md)
================================================
FILE: docker-compose.yml
================================================
version: '3.7'
services:
watchtower:
container_name: watchtower
build:
context: ./
dockerfile: dockerfiles/Dockerfile.dev-self-contained
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- 8080:8080
command: --interval 10 --http-api-metrics --http-api-token demotoken --debug prometheus grafana parent child
prometheus:
container_name: prometheus
image: prom/prometheus
volumes:
- ./prometheus/:/etc/prometheus/
- prometheus:/prometheus/
ports:
- 9090:9090
grafana:
container_name: grafana
image: grafana/grafana
ports:
- 3000:3000
environment:
GF_INSTALL_PLUGINS: grafana-clock-panel,grafana-simple-json-datasource
volumes:
- grafana:/var/lib/grafana
- ./grafana:/etc/grafana/provisioning
parent:
image: nginx
container_name: parent
child:
image: nginx:alpine
labels:
com.centurylinklabs.watchtower.depends-on: parent
container_name: child
volumes:
prometheus: {}
grafana: {}
================================================
FILE: dockerfiles/Dockerfile
================================================
FROM --platform=$BUILDPLATFORM alpine:3.19.0 as alpine
RUN apk add --no-cache \
ca-certificates \
tzdata
FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
COPY --from=alpine \
/etc/ssl/certs/ca-certificates.crt \
/etc/ssl/certs/ca-certificates.crt
COPY --from=alpine \
/usr/share/zoneinfo \
/usr/share/zoneinfo
EXPOSE 8080
COPY watchtower /
HEALTHCHECK CMD [ "/watchtower", "--health-check"]
ENTRYPOINT ["/watchtower"]
================================================
FILE: dockerfiles/Dockerfile.dev-self-contained
================================================
#
# Builder
#
FROM golang:alpine as builder
# use version (for example "v0.3.3") or "main"
ARG WATCHTOWER_VERSION=main
# Pre download required modules to avoid redownloading at each build thanks to docker layer caching.
# Copying go.mod and go.sum ensure to invalid the layer/build cache if there is a change in module requirement
WORKDIR /watchtower
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN apk add --no-cache \
alpine-sdk \
ca-certificates \
git \
tzdata
COPY . /watchtower
RUN \
cd /watchtower && \
\
GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -a -ldflags "-extldflags '-static' -X github.com/containrrr/watchtower/internal/meta.Version=$(git describe --tags)" . && \
GO111MODULE=on go test ./... -v
#
# watchtower
#
FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
# copy files from other container
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /watchtower/watchtower /watchtower
HEALTHCHECK CMD [ "/watchtower", "--health-check"]
ENTRYPOINT ["/watchtower"]
================================================
FILE: dockerfiles/Dockerfile.self-contained
================================================
#
# Builder
#
FROM golang:alpine as builder
# use version (for example "v0.3.3") or "main"
ARG WATCHTOWER_VERSION=main
RUN apk add --no-cache \
alpine-sdk \
ca-certificates \
git \
tzdata
RUN git clone --branch "${WATCHTOWER_VERSION}" https://github.com/containrrr/watchtower.git
RUN \
cd watchtower && \
\
GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -a -ldflags "-extldflags '-static' -X github.com/containrrr/watchtower/internal/meta.Version=$(git describe --tags)" . && \
GO111MODULE=on go test ./... -v
#
# watchtower
#
FROM scratch
LABEL "com.centurylinklabs.watchtower"="true"
# copy files from other container
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /go/watchtower/watchtower /watchtower
HEALTHCHECK CMD [ "/watchtower", "--health-check"]
ENTRYPOINT ["/watchtower"]
================================================
FILE: dockerfiles/container-networking/docker-compose.yml
================================================
services:
producer:
image: qmcgaw/gluetun:v3.35.0
cap_add:
- NET_ADMIN
environment:
- VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
- OPENVPN_USER=${OPENVPN_USER}
- OPENVPN_PASSWORD=${OPENVPN_PASSWORD}
- SERVER_COUNTRIES=${SERVER_COUNTRIES}
consumer:
depends_on:
- producer
image: nginx:1.25.1
network_mode: "service:producer"
labels:
- "com.centurylinklabs.watchtower.depends-on=/wt-contnet-producer-1"
================================================
FILE: docs/arguments.md
================================================
By default, watchtower will monitor all containers running within the Docker daemon to which it is pointed (in most cases this
will be the local Docker daemon, but you can override it with the `--host` option described in the next section). However, you
can restrict watchtower to monitoring a subset of the running containers by specifying the container names as arguments when
launching watchtower.
```bash
$ docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
nginx redis
```
In the example above, watchtower will only monitor the containers named "nginx" and "redis" for updates -- all of the other
running containers will be ignored. If you do not want watchtower to run as a daemon you can pass the `--run-once` flag and remove
the watchtower container after its execution.
```bash
$ docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
--run-once \
nginx redis
```
In the example above, watchtower will execute an upgrade attempt on the containers named "nginx" and "redis". Using this mode will enable debugging output showing all actions performed, as usage is intended for interactive users. Once the attempt is completed, the container will exit and remove itself due to the `--rm` flag.
When no arguments are specified, watchtower will monitor all running containers.
## Secrets/Files
Some arguments can also reference a file, in which case the contents of the file are used as the value.
This can be used to avoid putting secrets in the configuration file or command line.
The following arguments are currently supported (including their corresponding `WATCHTOWER_` environment variables):
- `notification-url`
- `notification-email-server-password`
- `notification-slack-hook-url`
- `notification-msteams-hook`
- `notification-gotify-token`
- `http-api-token`
### Example docker-compose usage
```yaml
secrets:
access_token:
file: access_token
services:
watchtower:
secrets:
- access_token
environment:
- WATCHTOWER_HTTP_API_TOKEN=/run/secrets/access_token
```
## Help
Shows documentation about the supported flags.
```text
Argument: --help
Environment Variable: N/A
Type: N/A
Default: N/A
```
## Time Zone
Sets the time zone to be used by WatchTower's logs and the optional Cron scheduling argument (--schedule). If this environment variable is not set, Watchtower will use the default time zone: UTC.
To find out the right value, see [this list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), find your location and use the value in _TZ Database Name_, e.g _Europe/Rome_. The timezone can alternatively be set by volume mounting your hosts /etc/localtime file. `-v /etc/localtime:/etc/localtime:ro`
```text
Argument: N/A
Environment Variable: TZ
Type: String
Default: "UTC"
```
## Cleanup
Removes old images after updating. When this flag is specified, watchtower will remove the old image after restarting a container with a new image. Use this option to prevent the accumulation of orphaned images on your system as containers are updated.
```text
Argument: --cleanup
Environment Variable: WATCHTOWER_CLEANUP
Type: Boolean
Default: false
```
## Remove anonymous volumes
Removes anonymous volumes after updating. When this flag is specified, watchtower will remove all anonymous volumes from the container before restarting with a new image. Named volumes will not be removed!
```text
Argument: --remove-volumes
Environment Variable: WATCHTOWER_REMOVE_VOLUMES
Type: Boolean
Default: false
```
## Debug
Enable debug mode with verbose logging.
!!! note "Notes"
Alias for `--log-level debug`. See [Maximum log level](#maximum-log-level).
Does _not_ take an argument when used as an argument. Using `--debug true` will **not** work.
```text
Argument: --debug, -d
Environment Variable: WATCHTOWER_DEBUG
Type: Boolean
Default: false
```
## Trace
Enable trace mode with very verbose logging. Caution: exposes credentials!
!!! note "Notes"
Alias for `--log-level trace`. See [Maximum log level](#maximum-log-level).
Does _not_ take an argument when used as an argument. Using `--trace true` will **not** work.
```text
Argument: --trace
Environment Variable: WATCHTOWER_TRACE
Type: Boolean
Default: false
```
## Maximum log level
The maximum log level that will be written to STDERR (shown in `docker log` when used in a container).
```text
Argument: --log-level
Environment Variable: WATCHTOWER_LOG_LEVEL
Possible values: panic, fatal, error, warn, info, debug or trace
Default: info
```
## Logging format
Sets what logging format to use for console output.
```text
Argument: --log-format, -l
Environment Variable: WATCHTOWER_LOG_FORMAT
Possible values: Auto, LogFmt, Pretty or JSON
Default: Auto
```
## ANSI colors
Disable ANSI color escape codes in log output.
```text
Argument: --no-color
Environment Variable: NO_COLOR
Type: Boolean
Default: false
```
## Docker host
Docker daemon socket to connect to. Can be pointed at a remote Docker host by specifying a TCP endpoint as "tcp://hostname:port".
```text
Argument: --host, -H
Environment Variable: DOCKER_HOST
Type: String
Default: "unix:///var/run/docker.sock"
```
## Docker API version
The API version to use by the Docker client for connecting to the Docker daemon. The minimum supported version is 1.24.
```text
Argument: --api-version, -a
Environment Variable: DOCKER_API_VERSION
Type: String
Default: "1.24"
```
## Include restarting
Will also include restarting containers.
```text
Argument: --include-restarting
Environment Variable: WATCHTOWER_INCLUDE_RESTARTING
Type: Boolean
Default: false
```
## Include stopped
Will also include created and exited containers.
```text
Argument: --include-stopped, -S
Environment Variable: WATCHTOWER_INCLUDE_STOPPED
Type: Boolean
Default: false
```
## Revive stopped
Start any stopped containers that have had their image updated. This argument is only usable with the `--include-stopped` argument.
```text
Argument: --revive-stopped
Environment Variable: WATCHTOWER_REVIVE_STOPPED
Type: Boolean
Default: false
```
## Poll interval
Poll interval (in seconds). This value controls how frequently watchtower will poll for new images. Either `--schedule` or a poll interval can be defined, but not both.
```text
Argument: --interval, -i
Environment Variable: WATCHTOWER_POLL_INTERVAL
Type: Integer
Default: 86400 (24 hours)
```
## Filter by enable label
Monitor and update containers that have a `com.centurylinklabs.watchtower.enable` label set to true.
```text
Argument: --label-enable
Environment Variable: WATCHTOWER_LABEL_ENABLE
Type: Boolean
Default: false
```
## Filter by disable label
__Do not__ Monitor and update containers that have `com.centurylinklabs.watchtower.enable` label set to false and
no `--label-enable` argument is passed. Note that only one or the other (targeting by enable label) can be
used at the same time to target containers.
## Filter by disabling specific container names
Monitor and update containers whose names are not in a given set of names.
This can be used to exclude specific containers, when setting labels is not an option.
The listed containers will be excluded even if they have the enable filter set to true.
```text
Argument: --disable-containers, -x
Environment Variable: WATCHTOWER_DISABLE_CONTAINERS
Type: Comma- or space-separated string list
Default: ""
```
## Without updating containers
Will only monitor for new images, send notifications and invoke
the [pre-check/post-check hooks](https://containrrr.dev/watchtower/lifecycle-hooks/), but will __not__ update the
containers.
!!! note
Due to Docker API limitations the latest image will still be pulled from the registry.
The HEAD digest checks allows watchtower to skip pulling when there are no changes, but to know _what_ has changed it
will still do a pull whenever the repository digest doesn't match the local image digest.
```text
Argument: --monitor-only
Environment Variable: WATCHTOWER_MONITOR_ONLY
Type: Boolean
Default: false
```
Note that monitor-only can also be specified on a per-container basis with the `com.centurylinklabs.watchtower.monitor-only` label set on those containers.
See [With label taking precedence over arguments](#With-label-taking-precedence-over-arguments) for behavior when both argument and label are set
## With label taking precedence over arguments
By default, arguments will take precedence over labels. This means that if you set `WATCHTOWER_MONITOR_ONLY` to true or use `--monitor-only`, a container with `com.centurylinklabs.watchtower.monitor-only` set to false will not be updated. If you set `WATCHTOWER_LABEL_TAKE_PRECEDENCE` to true or use `--label-take-precedence`, then the container will also be updated. This also apply to the no pull option. if you set `WATCHTOWER_NO_PULL` to true or use `--no-pull`, a container with `com.centurylinklabs.watchtower.no-pull` set to false will not pull the new image. If you set `WATCHTOWER_LABEL_TAKE_PRECEDENCE` to true or use `--label-take-precedence`, then the container will pull image
```text
Argument: --label-take-precedence
Environment Variable: WATCHTOWER_LABEL_TAKE_PRECEDENCE
Type: Boolean
Default: false
```
## Without restarting containers
Do not restart containers after updating. This option can be useful when the start of the containers
is managed by an external system such as systemd.
```text
Argument: --no-restart
Environment Variable: WATCHTOWER_NO_RESTART
Type: Boolean
Default: false
```
## Without pulling new images
Do not pull new images. When this flag is specified, watchtower will not attempt to pull
new images from the registry. Instead it will only monitor the local image cache for changes.
Use this option if you are building new images directly on the Docker host without pushing
them to a registry.
```text
Argument: --no-pull
Environment Variable: WATCHTOWER_NO_PULL
Type: Boolean
Default: false
```
Note that no-pull can also be specified on a per-container basis with the
`com.centurylinklabs.watchtower.no-pull` label set on those containers.
See [With label taking precedence over arguments](#With-label-taking-precedence-over-arguments) for behavior when both argument and label are set
## Without sending a startup message
Do not send a message after watchtower started. Otherwise there will be an info-level notification.
```text
Argument: --no-startup-message
Environment Variable: WATCHTOWER_NO_STARTUP_MESSAGE
Type: Boolean
Default: false
```
## Run once
Run an update attempt against a container name list one time immediately and exit.
```text
Argument: --run-once, -R
Environment Variable: WATCHTOWER_RUN_ONCE
Type: Boolean
Default: false
```
## HTTP API Mode
Runs Watchtower in HTTP API mode, only allowing image updates to be triggered by an HTTP request.
For details see [HTTP API](https://containrrr.dev/watchtower/http-api-mode).
```text
Argument: --http-api-update
Environment Variable: WATCHTOWER_HTTP_API_UPDATE
Type: Boolean
Default: false
```
## HTTP API Token
Sets an authentication token to HTTP API requests.
Can also reference a file, in which case the contents of the file are used.
```text
Argument: --http-api-token
Environment Variable: WATCHTOWER_HTTP_API_TOKEN
Type: String
Default: -
```
## HTTP API periodic polls
Keep running periodic updates if the HTTP API mode is enabled, otherwise the HTTP API would prevent periodic polls.
```text
Argument: --http-api-periodic-polls
Environment Variable: WATCHTOWER_HTTP_API_PERIODIC_POLLS
Type: Boolean
Default: false
```
## Filter by scope
Update containers that have a `com.centurylinklabs.watchtower.scope` label set with the same value as the given argument.
This enables [running multiple instances](https://containrrr.dev/watchtower/running-multiple-instances).
!!! note "Filter by lack of scope"
If you want other instances of watchtower to ignore the scoped containers, set this argument to `none`.
When omitted, watchtower will update all containers regardless of scope.
```text
Argument: --scope
Environment Variable: WATCHTOWER_SCOPE
Type: String
Default: -
```
## HTTP API Metrics
Enables a metrics endpoint, exposing prometheus metrics via HTTP. See [Metrics](metrics.md) for details.
```text
Argument: --http-api-metrics
Environment Variable: WATCHTOWER_HTTP_API_METRICS
Type: Boolean
Default: false
```
## Scheduling
[Cron expression](https://pkg.go.dev/github.com/robfig/cron@v1.2.0?tab=doc#hdr-CRON_Expression_Format) in 6 fields (rather than the traditional 5) which defines when and how often to check for new images. Either `--interval` or the schedule expression
can be defined, but not both. An example: `--schedule "0 0 4 * * *"`
```text
Argument: --schedule, -s
Environment Variable: WATCHTOWER_SCHEDULE
Type: String
Default: -
```
## Rolling restart
Restart one image at time instead of stopping and starting all at once. Useful in conjunction with lifecycle hooks
to implement zero-downtime deploy.
```text
Argument: --rolling-restart
Environment Variable: WATCHTOWER_ROLLING_RESTART
Type: Boolean
Default: false
```
## Wait until timeout
Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.
```text
Argument: --stop-timeout
Environment Variable: WATCHTOWER_TIMEOUT
Type: Duration
Default: 10s
```
## TLS Verification
Use TLS when connecting to the Docker socket and verify the server's certificate. See below for options used to
configure notifications.
```text
Argument: --tlsverify
Environment Variable: DOCKER_TLS_VERIFY
Type: Boolean
Default: false
```
## HEAD failure warnings
When to warn about HEAD pull requests failing. Auto means that it will warn when the registry is known to handle the
requests and may rate limit pull requests (mainly docker.io).
```text
Argument: --warn-on-head-failure
Environment Variable: WATCHTOWER_WARN_ON_HEAD_FAILURE
Possible values: always, auto, never
Default: auto
```
## Health check
Returns a success exit code to enable usage with docker `HEALTHCHECK`. This check is naive and only returns checks whether there is another process running inside the container, as it is the only known form of failure state for watchtowers container.
!!! note "Only for HEALTHCHECK use"
Never put this on the main container executable command line as it is only meant to be run from docker HEALTHCHECK.
```text
Argument: --health-check
```
## Programatic Output (porcelain)
Writes the session results to STDOUT using a stable, machine-readable format (indicated by the argument VERSION).
Alias for:
```text
--notification-url logger://
--notification-log-stdout
--notification-report
--notification-template porcelain.VERSION.summary-no-log
Argument: --porcelain, -P
Environment Variable: WATCHTOWER_PORCELAIN
Possible values: v1
Default: -
```
================================================
FILE: docs/container-selection.md
================================================
By default, watchtower will watch all containers. However, sometimes only some containers should be updated.
There are two options:
- **Fully exclude**: You can choose to exclude containers entirely from being watched by watchtower.
- **Monitor only**: In this mode, watchtower checks for container updates, sends notifications and invokes the [pre-check/post-check hooks](https://containrrr.dev/watchtower/lifecycle-hooks/) on the containers but does **not** perform the update.
## Full Exclude
If you need to exclude some containers, set the _com.centurylinklabs.watchtower.enable_ label to `false`. For clarity this should be set **on the container(s)** you wish to be ignored, this is not set on watchtower.
=== "dockerfile"
```docker
LABEL com.centurylinklabs.watchtower.enable="false"
```
=== "docker run"
```bash
docker run -d --label=com.centurylinklabs.watchtower.enable=false someimage
```
=== "docker-compose"
``` yaml
version: "3"
services:
someimage:
container_name: someimage
labels:
- "com.centurylinklabs.watchtower.enable=false"
```
If instead you want to [only include containers with the enable label](https://containrrr.github.io/watchtower/arguments/#filter_by_enable_label), pass the `--label-enable` flag or the `WATCHTOWER_LABEL_ENABLE` environment variable on startup for watchtower and set the _com.centurylinklabs.watchtower.enable_ label with a value of `true` on the containers you want to watch.
=== "dockerfile"
```docker
LABEL com.centurylinklabs.watchtower.enable="true"
```
=== "docker run"
```bash
docker run -d --label=com.centurylinklabs.watchtower.enable=true someimage
```
=== "docker-compose"
``` yaml
version: "3"
services:
someimage:
container_name: someimage
labels:
- "com.centurylinklabs.watchtower.enable=true"
```
If you wish to create a monitoring scope, you will need to [run multiple instances and set a scope for each of them](https://containrrr.github.io/watchtower/running-multiple-instances).
Watchtower filters running containers by testing them against each configured criteria. A container is monitored if all criteria are met. For example:
- If a container's name is on the monitoring name list (not empty `--name` argument) but it is not enabled (_centurylinklabs.watchtower.enable=false_), it won't be monitored;
- If a container's name is not on the monitoring name list (not empty `--name` argument), even if it is enabled (_centurylinklabs.watchtower.enable=true_ and `--label-enable` flag is set), it won't be monitored;
## Monitor Only
Individual containers can be marked to only be monitored (without being updated).
To do so, set the *com.centurylinklabs.watchtower.monitor-only* label to `true` on that container.
```docker
LABEL com.centurylinklabs.watchtower.monitor-only="true"
```
Or, it can be specified as part of the `docker run` command line:
```bash
docker run -d --label=com.centurylinklabs.watchtower.monitor-only=true someimage
```
When the label is specified on a container, watchtower treats that container exactly as if [`WATCHTOWER_MONITOR_ONLY`](https://containrrr.dev/watchtower/arguments/#without_updating_containers) was set, but the effect is limited to the individual container.
================================================
FILE: docs/http-api-mode.md
================================================
Watchtower provides an HTTP API mode that enables an HTTP endpoint that can be requested to trigger container updating. The current available endpoint list is:
- `/v1/update` - triggers an update for all of the containers monitored by this Watchtower instance.
---
To enable this mode, use the flag `--http-api-update`. For example, in a Docker Compose config file:
```yaml
version: '3'
services:
app-monitored-by-watchtower:
image: myapps/monitored-by-watchtower
labels:
- "com.centurylinklabs.watchtower.enable=true"
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --debug --http-api-update
environment:
- WATCHTOWER_HTTP_API_TOKEN=mytoken
labels:
- "com.centurylinklabs.watchtower.enable=false"
ports:
- 8080:8080
```
By default, enabling this mode prevents periodic polls (i.e. what is specified using `--interval` or `--schedule`). To run periodic updates regardless, pass `--http-api-periodic-polls`.
Notice that there is an environment variable named WATCHTOWER_HTTP_API_TOKEN. To prevent external services from accidentally triggering image updates, all of the requests have to contain a "Token" field, valued as the token defined in WATCHTOWER_HTTP_API_TOKEN, in their headers. In this case, there is a port bind to the host machine, allowing to request localhost:8080 to reach Watchtower. The following `curl` command would trigger an image update:
```bash
curl -H "Authorization: Bearer mytoken" localhost:8080/v1/update
```
---
In order to update only certain images, the image names can be provided as URL query parameters. The following `curl` command would trigger an update for the images `foo/bar` and `foo/baz`:
```bash
curl -H "Authorization: Bearer mytoken" localhost:8080/v1/update?image=foo/bar,foo/baz
```
================================================
FILE: docs/index.md
================================================
<p style="text-align: center; margin-left: 1.6rem;">
<img alt="Logotype depicting a lighthouse" src="./images/logo-450px.png" width="450" />
</p>
<h1 align="center">
Watchtower
</h1>
<p align="center">
A container-based solution for automating Docker container base image updates.
<br/><br/>
<a href="https://circleci.com/gh/containrrr/watchtower">
<img alt="Circle CI" src="https://circleci.com/gh/containrrr/watchtower.svg?style=shield" />
</a>
<a href="https://codecov.io/gh/containrrr/watchtower">
<img alt="Codecov" src="https://codecov.io/gh/containrrr/watchtower/branch/main/graph/badge.svg">
</a>
<a href="https://godoc.org/github.com/containrrr/watchtower">
<img alt="GoDoc" src="https://godoc.org/github.com/containrrr/watchtower?status.svg" />
</a>
<a href="https://goreportcard.com/report/github.com/containrrr/watchtower">
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/containrrr/watchtower" />
</a>
<a href="https://github.com/containrrr/watchtower/releases">
<img alt="latest version" src="https://img.shields.io/github/tag/containrrr/watchtower.svg" />
</a>
<a href="https://www.apache.org/licenses/LICENSE-2.0">
<img alt="Apache-2.0 License" src="https://img.shields.io/github/license/containrrr/watchtower.svg" />
</a>
<a href="https://www.codacy.com/gh/containrrr/watchtower/dashboard?utm_source=github.com&utm_medium=referral&utm_content=containrrr/watchtower&utm_campaign=Badge_Grade">
<img alt="Codacy Badge" src="https://app.codacy.com/project/badge/Grade/1c48cfb7646d4009aa8c6f71287670b8"/>
</a>
<a href="https://github.com/containrrr/watchtower/#contributors">
<img alt="All Contributors" src="https://img.shields.io/github/all-contributors/containrrr/watchtower" />
</a>
<a href="https://hub.docker.com/r/containrrr/watchtower">
<img alt="Pulls from DockerHub" src="https://img.shields.io/docker/pulls/containrrr/watchtower.svg" />
</a>
</p>
## Quick Start
With watchtower you can update the running version of your containerized app simply by pushing a new image to the Docker
Hub or your own image registry. Watchtower will pull down your new image, gracefully shut down your existing container
and restart it with the same options that were used when it was deployed initially. Run the watchtower container with
the following command:
=== "docker run"
```bash
$ docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower
```
=== "docker-compose.yml"
```yaml
version: "3"
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
```
================================================
FILE: docs/introduction.md
================================================
Watchtower is an application that will monitor your running Docker containers and watch for changes to the images that those containers were originally started from. If watchtower detects that an image has changed, it will automatically restart the container using the new image.
With watchtower you can update the running version of your containerized app simply by pushing a new image to the Docker Hub or your own image registry. Watchtower will pull down your new image, gracefully shut down your existing container and restart it with the same options that were used when it was deployed initially.
For example, let's say you were running watchtower along with an instance of _centurylink/wetty-cli_ image:
```text
$ docker ps
CONTAINER ID IMAGE STATUS PORTS NAMES
967848166a45 centurylink/wetty-cli Up 10 minutes 0.0.0.0:8080->3000/tcp wetty
6cc4d2a9d1a5 containrrr/watchtower Up 15 minutes watchtower
```
Every day watchtower will pull the latest _centurylink/wetty-cli_ image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
================================================
FILE: docs/lifecycle-hooks.md
================================================
## Executing commands before and after updating
!!! note
These are shell commands executed with `sh`, and therefore require the container to provide the `sh`
executable.
> **DO NOTE**: If the container is not running then lifecycle hooks can not run and therefore
> the update is executed without running any lifecycle hooks.
It is possible to execute _pre/post\-check_ and _pre/post\-update_ commands
**inside** every container updated by watchtower.
- The _pre-check_ command is executed for each container prior to every update cycle.
- The _pre-update_ command is executed before stopping the container when an update is about to start.
- The _post-update_ command is executed after restarting the updated container
- The _post-check_ command is executed for each container post every update cycle.
This feature is disabled by default. To enable it, you need to set the option
`--enable-lifecycle-hooks` on the command line, or set the environment variable
`WATCHTOWER_LIFECYCLE_HOOKS` to `true`.
### Specifying update commands
The commands are specified using docker container labels, the following are currently available:
| Type | Docker Container Label |
| ----------- | ------------------------------------------------------ |
| Pre Check | `com.centurylinklabs.watchtower.lifecycle.pre-check` |
| Pre Update | `com.centurylinklabs.watchtower.lifecycle.pre-update` |
| Post Update | `com.centurylinklabs.watchtower.lifecycle.post-update` |
| Post Check | `com.centurylinklabs.watchtower.lifecycle.post-check` |
These labels can be declared as instructions in a Dockerfile (with some example .sh files) or be specified as part of
the `docker run` command line:
=== "Dockerfile"
```docker
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="/sync.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh"
LABEL com.centurylinklabs.watchtower.lifecycle.post-check="/send-heartbeat.sh"
```
=== "docker run"
```bash
docker run -d \
--label=com.centurylinklabs.watchtower.lifecycle.pre-check="/sync.sh" \
--label=com.centurylinklabs.watchtower.lifecycle.pre-update="/dump-data.sh" \
--label=com.centurylinklabs.watchtower.lifecycle.post-update="/restore-data.sh" \
someimage --label=com.centurylinklabs.watchtower.lifecycle.post-check="/send-heartbeat.sh" \
```
### Timeouts
The timeout for all lifecycle commands is 60 seconds. After that, a timeout will
occur, forcing Watchtower to continue the update loop.
#### Pre- or Post-update timeouts
For the `pre-update` or `post-update` lifecycle command, it is possible to override this timeout to
allow the script to finish before forcefully killing it. This is done by adding the
label `com.centurylinklabs.watchtower.lifecycle.pre-update-timeout` or post-update-timeout respectively followed by
the timeout expressed in minutes.
If the label value is explicitly set to `0`, the timeout will be disabled.
### Execution failure
The failure of a command to execute, identified by an exit code different than
0 or 75 (EX_TEMPFAIL), will not prevent watchtower from updating the container. Only an error
log statement containing the exit code will be reported.
================================================
FILE: docs/linked-containers.md
================================================
Watchtower will detect if there are links between any of the running containers and ensures that things are stopped/started in a way that won't break any of the links. If an update is detected for one of the dependencies in a group of linked containers, watchtower will stop and start all of the containers in the correct order so that the application comes back up correctly.
For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work.
If you want to override existing links, or if you are not using links, you can use special `com.centurylinklabs.watchtower.depends-on` label with dependent container names, separated by a comma.
When you have a depending container that is using `network_mode: service:container` then watchtower will treat that container as an implicit link.
================================================
FILE: docs/metrics.md
================================================
!!! warning "Experimental feature"
This feature was added in v1.0.4 and is still considered experimental. If you notice any strange behavior, please raise
a ticket in the repository issues.
Metrics can be used to track how Watchtower behaves over time.
To use this feature, you have to set an [API token](arguments.md#http_api_token) and [enable the metrics API](arguments.md#http_api_metrics),
as well as creating a port mapping for your container for port `8080`.
The metrics API endpoint is `/v1/metrics`.
## Available Metrics
| Name | Type | Description |
| ------------------------------- | ------- | --------------------------------------------------------------------------- |
| `watchtower_containers_scanned` | Gauge | Number of containers scanned for changes by watchtower during the last scan |
| `watchtower_containers_updated` | Gauge | Number of containers updated by watchtower during the last scan |
| `watchtower_containers_failed` | Gauge | Number of containers where update failed during the last scan |
| `watchtower_scans_total` | Counter | Number of scans since the watchtower started |
| `watchtower_scans_skipped` | Counter | Number of skipped scans since watchtower started |
## Example Prometheus `scrape_config`
```yaml
scrape_configs:
- job_name: watchtower
scrape_interval: 5s
metrics_path: /v1/metrics
bearer_token: demotoken
static_configs:
- targets:
- 'watchtower:8080'
```
Replace `demotoken` with the Bearer token you have set accordingly.
## Demo
The repository contains a demo with prometheus and grafana, available through `docker-compose.yml`. This demo
is preconfigured with a dashboard, which will look something like this:

================================================
FILE: docs/notifications.md
================================================
# Notifications
Watchtower can send notifications when containers are updated. Notifications are sent via hooks in the logging
system, [logrus](http://github.com/sirupsen/logrus).
!!! note "Using multiple notifications with environment variables"
There is currently a bug in Viper (https://github.com/spf13/viper/issues/380), which prevents comma-separated slices to
be used when using the environment variable.
A workaround is available where we instead put quotes around the environment variable value and replace the commas with
spaces:
```
WATCHTOWER_NOTIFICATIONS="slack msteams"
```
If you're a `docker-compose` user, make sure to specify environment variables' values in your `.yml` file without double
quotes (`"`). This prevents unexpected errors when watchtower starts.
## Settings
- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info`, `debug` or `trace`.
- `--notifications-hostname` (env. `WATCHTOWER_NOTIFICATIONS_HOSTNAME`): Custom hostname specified in subject/title. Useful to override the operating system hostname.
- `--notifications-delay` (env. `WATCHTOWER_NOTIFICATIONS_DELAY`): Delay before sending notifications expressed in seconds.
- Watchtower will post a notification every time it is started. This behavior [can be changed](https://containrrr.github.io/watchtower/arguments/#without_sending_a_startup_message) with an argument.
- `--notification-title-tag` (env. `WATCHTOWER_NOTIFICATION_TITLE_TAG`): Prefix to include in the title. Useful when running multiple watchtowers.
- `--notification-skip-title` (env. `WATCHTOWER_NOTIFICATION_SKIP_TITLE`): Do not pass the title param to notifications. This will not pass a dynamic title override to notification services. If no title is configured for the service, it will remove the title all together.
- `--notification-log-stdout` (env. `WATCHTOWER_NOTIFICATION_LOG_STDOUT`): Enable output from `logger://` shoutrrr service to stdout.
## [Shoutrrr](https://github.com/containrrr/shoutrrr) notifications
To send notifications via shoutrrr, the following command-line options, or their corresponding environment variables, can be set:
- `--notification-url` (env. `WATCHTOWER_NOTIFICATION_URL`): The shoutrrr service URL to be used. This option can also reference a file, in which case the contents of the file are used.
Go to [containrrr.dev/shoutrrr/v0.8/services/overview](https://containrrr.dev/shoutrrr/v0.8/services/overview) to
learn more about the different service URLs you can use. You can define multiple services by space separating the
URLs. (See example below)
You can customize the message posted by setting a template.
- `--notification-template` (env. `WATCHTOWER_NOTIFICATION_TEMPLATE`): The template used for the message.
The template is a Go [template](https://golang.org/pkg/text/template/) that either format a list
of [log entries](https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry) or a `notification.Data` struct.
Simple templates are used unless the `notification-report` flag is specified:
- `--notification-report` (env. `WATCHTOWER_NOTIFICATION_REPORT`): Use the session report as the notification template data.
## Simple templates
The default value if not set is `{{range .}}{{.Message}}{{println}}{{end}}`. The example below uses a template that also
outputs timestamp and log level.
!!! tip "Custom date format"
If you want to adjust the date/time format it must show how the
[reference time](https://golang.org/pkg/time/#pkg-constants) (_Mon Jan 2 15:04:05 MST 2006_) would be displayed in your
custom format.
i.e., The day of the year has to be 1, the month has to be 2 (february), the hour 3 (or 15 for 24h time) etc.
!!! note "Skipping notifications"
To skip sending notifications that do not contain any information, you can wrap your template with `{{if .}}` and `{{end}}`.
Example:
```bash
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATION_URL="discord://token@channel slack://watchtower@token-a/token-b/token-c" \
-e WATCHTOWER_NOTIFICATION_TEMPLATE="{{range .}}{{.Time.Format \"2006-01-02 15:04:05\"}} ({{.Level}}): {{.Message}}{{println}}{{end}}" \
containrrr/watchtower
```
## Report templates
The default template for report notifications are the following:
```go
{{- if .Report -}}
{{- with .Report -}}
{{- if ( or .Updated .Failed ) -}}
{{len .Scanned}} Scanned, {{len .Updated}} Updated, {{len .Failed}} Failed
{{- range .Updated}}
- {{.Name}} ({{.ImageName}}): {{.CurrentImageID.ShortID}} updated to {{.LatestImageID.ShortID}}
{{- end -}}
{{- range .Fresh}}
- {{.Name}} ({{.ImageName}}): {{.State}}
{{- end -}}
{{- range .Skipped}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- range .Failed}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- else -}}
{{range .Entries -}}{{.Message}}{{"\n"}}{{- end -}}
{{- end -}}
```
It will be used to send a summary of every session if there are any containers that were updated or which failed to update.
!!! note "Skipping notifications"
Whenever the result of applying the template results in an empty string, no notifications will
be sent. This is by default used to limit the notifications to only be sent when there something noteworthy occurred.
You can replace `{{- if ( or .Updated .Failed ) -}}` with any logic you want to decide when to send the notifications.
Example using a custom report template that always sends a session report after each run:
=== "docker run"
```bash
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATION_REPORT="true" \
-e WATCHTOWER_NOTIFICATION_URL="discord://token@channel slack://watchtower@token-a/token-b/token-c" \
-e WATCHTOWER_NOTIFICATION_TEMPLATE="
{{- if .Report -}}
{{- with .Report -}}
{{len .Scanned}} Scanned, {{len .Updated}} Updated, {{len .Failed}} Failed
{{- range .Updated}}
- {{.Name}} ({{.ImageName}}): {{.CurrentImageID.ShortID}} updated to {{.LatestImageID.ShortID}}
{{- end -}}
{{- range .Fresh}}
- {{.Name}} ({{.ImageName}}): {{.State}}
{{- end -}}
{{- range .Skipped}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- range .Failed}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- end -}}
{{- else -}}
{{range .Entries -}}{{.Message}}{{\"\n\"}}{{- end -}}
{{- end -}}
" \
containrrr/watchtower
```
=== "docker-compose"
``` yaml
version: "3"
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
env:
WATCHTOWER_NOTIFICATION_REPORT: "true"
WATCHTOWER_NOTIFICATION_URL: >
discord://token@channel
slack://watchtower@token-a/token-b/token-c
WATCHTOWER_NOTIFICATION_TEMPLATE: |
{{- if .Report -}}
{{- with .Report -}}
{{len .Scanned}} Scanned, {{len .Updated}} Updated, {{len .Failed}} Failed
{{- range .Updated}}
- {{.Name}} ({{.ImageName}}): {{.CurrentImageID.ShortID}} updated to {{.LatestImageID.ShortID}}
{{- end -}}
{{- range .Fresh}}
- {{.Name}} ({{.ImageName}}): {{.State}}
{{- end -}}
{{- range .Skipped}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- range .Failed}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- end -}}
{{- else -}}
{{range .Entries -}}{{.Message}}{{"\n"}}{{- end -}}
{{- end -}}
```
## Legacy notifications
For backwards compatibility, the notifications can also be configured using legacy notification options. These will automatically be converted to shoutrrr URLs when used.
The types of notifications to send are set by passing a comma-separated list of values to the `--notifications` option
(or corresponding environment variable `WATCHTOWER_NOTIFICATIONS`), which has the following valid values:
- `email` to send notifications via e-mail
- `slack` to send notifications through a Slack webhook
- `msteams` to send notifications via MSTeams webhook
- `gotify` to send notifications via Gotify
### `notify-upgrade`
If watchtower is started with `notify-upgrade` as it's first argument, it will generate a .env file with your current legacy notification options converted to shoutrrr URLs.
=== "docker run"
```bash
$ docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=slack \
-e WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy" \
containrrr/watchtower \
notify-upgrade
```
=== "docker-compose.yml"
```yaml
version: "3"
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
env:
WATCHTOWER_NOTIFICATIONS: slack
WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL: https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy
command: notify-upgrade
```
You can then copy this file from the container (a message with the full command to do so will be logged) and use it with your current setup:
=== "docker run"
```bash
$ docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
--env-file watchtower-notifications.env \
containrrr/watchtower
```
=== "docker-compose.yml"
```yaml
version: "3"
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
env_file:
- watchtower-notifications.env
```
### Email
To receive notifications by email, the following command-line options, or their corresponding environment variables, can be set:
- `--notification-email-from` (env. `WATCHTOWER_NOTIFICATION_EMAIL_FROM`): The e-mail address from which notifications will be sent.
- `--notification-email-to` (env. `WATCHTOWER_NOTIFICATION_EMAIL_TO`): The e-mail address to which notifications will be sent.
- `--notification-email-server` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER`): The SMTP server to send e-mails through.
- `--notification-email-server-tls-skip-verify` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY`): Do not verify the TLS certificate of the mail server. This should be used only for testing.
- `--notification-email-server-port` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT`): The port used to connect to the SMTP server to send e-mails through. Defaults to `25`.
- `--notification-email-server-user` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER`): The username to authenticate with the SMTP server with.
- `--notification-email-server-password` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD`): The password to authenticate with the SMTP server with. Can also reference a file, in which case the contents of the file are used.
- `--notification-email-delay` (env. `WATCHTOWER_NOTIFICATION_EMAIL_DELAY`): Delay before sending notifications expressed in seconds.
- `--notification-email-subjecttag` (env. `WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG`): Prefix to include in the subject tag. Useful when running multiple watchtowers. **NOTE:** This will affect all notification types.
Example:
```bash
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=email \
-e WATCHTOWER_NOTIFICATION_EMAIL_FROM=fromaddress@gmail.com \
-e WATCHTOWER_NOTIFICATION_EMAIL_TO=toaddress@gmail.com \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.gmail.com \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587 \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=fromaddress@gmail.com \
-e WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=app_password \
-e WATCHTOWER_NOTIFICATION_EMAIL_DELAY=2 \
containrrr/watchtower
```
The previous example assumes, that you already have an SMTP server up and running you can connect to. If you don't or you want to bring up watchtower with your own simple SMTP relay the following `docker-compose.yml` might be a good start for you.
The following example assumes, that your domain is called `your-domain.com` and that you are going to use a certificate valid for `smtp.your-domain.com`. This hostname has to be used as `WATCHTOWER_NOTIFICATION_EMAIL_SERVER` otherwise the TLS connection is going to fail with `Failed to send notification email` or `connect: connection refused`. We also have to add a network for this setup in order to add an alias to it. If you also want to enable DKIM or other features on the SMTP server, you will find more information at [freinet/postfix-relay](https://hub.docker.com/r/freinet/postfix-relay).
Example including an SMTP relay:
```yaml
version: '3.8'
services:
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
environment:
WATCHTOWER_MONITOR_ONLY: 'true'
WATCHTOWER_NOTIFICATIONS: email
WATCHTOWER_NOTIFICATION_EMAIL_FROM: from-address@your-domain.com
WATCHTOWER_NOTIFICATION_EMAIL_TO: to-address@your-domain.com
# you have to use a network alias here, if you use your own certificate
WATCHTOWER_NOTIFICATION_EMAIL_SERVER: smtp.your-domain.com
WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT: 25
WATCHTOWER_NOTIFICATION_EMAIL_DELAY: 2
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- watchtower
depends_on:
- postfix
# SMTP needed to send out status emails
postfix:
image: freinet/postfix-relay:latest
expose:
- 25
environment:
MAILNAME: somename.your-domain.com
TLS_KEY: '/etc/ssl/domains/your-domain.com/your-domain.com.key'
TLS_CRT: '/etc/ssl/domains/your-domain.com/your-domain.com.crt'
TLS_CA: '/etc/ssl/domains/your-domain.com/intermediate.crt'
volumes:
- /etc/ssl/domains/your-domain.com/:/etc/ssl/domains/your-domain.com/:ro
networks:
watchtower:
# this alias is really important to make your certificate work
aliases:
- smtp.your-domain.com
networks:
watchtower:
external: false
```
### Slack
To receive notifications in Slack, add `slack` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
Additionally, you should set the Slack webhook URL using the `--notification-slack-hook-url` option or the `WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL` environment variable. This option can also reference a file, in which case the contents of the file are used.
By default, watchtower will send messages under the name `watchtower`, you can customize this string through the `--notification-slack-identifier` option or the `WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER` environment variable.
Other, optional, variables include:
- `--notification-slack-channel` (env. `WATCHTOWER_NOTIFICATION_SLACK_CHANNEL`): A string which overrides the webhook's default channel. Example: #my-custom-channel.
Example:
```bash
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=slack \
-e WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL="https://hooks.slack.com/services/xxx/yyyyyyyyyyyyyyy" \
-e WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=watchtower-server-1 \
-e WATCHTOWER_NOTIFICATION_SLACK_CHANNEL=#my-custom-channel \
containrrr/watchtower
```
### Microsoft Teams
To receive notifications in MSTeams channel, add `msteams` to the `--notifications` option or the `WATCHTOWER_NOTIFICATIONS` environment variable.
Additionally, you should set the MSTeams webhook URL using the `--notification-msteams-hook` option or the `WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL` environment variable. This option can also reference a file, in which case the contents of the file are used.
MSTeams notifier could send keys/values filled by `log.WithField` or `log.WithFields` as MSTeams message facts. To enable this feature add `--notification-msteams-data` flag or set `WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true` environment variable.
Example:
```bash
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=msteams \
-e WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL="https://outlook.office.com/webhook/xxxxxxxx@xxxxxxx/IncomingWebhook/yyyyyyyy/zzzzzzzzzz" \
-e WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true \
containrrr/watchtower
```
### Gotify
To push a notification to your Gotify instance, register a Gotify app and specify the Gotify URL and app token:
```bash
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_NOTIFICATIONS=gotify \
-e WATCHTOWER_NOTIFICATION_GOTIFY_URL="https://my.gotify.tld/" \
-e WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN="SuperSecretToken" \
containrrr/watchtower
```
`-e WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN` or `--notification-gotify-token` can also reference a file, in which case the contents of the file are used.
If you want to disable TLS verification for the Gotify instance, you can use either `-e WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY=true` or `--notification-gotify-tls-skip-verify`.
================================================
FILE: docs/private-registries.md
================================================
Watchtower supports private Docker image registries. In many cases, accessing a private registry
requires a valid username and password (i.e., _credentials_). In order to operate in such an
environment, watchtower needs to know the credentials to access the registry.
The credentials can be provided to watchtower in a configuration file called `config.json`.
There are two ways to generate this configuration file:
* The configuration file can be created manually.
* Call `docker login <REGISTRY_NAME>` and share the resulting configuration file.
### Create the configuration file manually
Create a new configuration file with the following syntax and a base64 encoded username and
password `auth` string:
```json
{
"auths": {
"<REGISTRY_NAME>": {
"auth": "XXXXXXX"
}
}
}
```
`<REGISTRY_NAME>` needs to be replaced by the name of your private registry
(e.g., `my-private-registry.example.org`).
!!! info "Using private images on Docker Hub"
To access private repositories on Docker Hub,
`<REGISTRY_NAME>` should be `https://index.docker.io/v1/`.
In this special case, the registry domain does not have to be specified
in `docker run` or `docker-compose`. Like Docker, Watchtower will use the
Docker Hub registry and its credentials when no registry domain is specified.
<sub>Watchtower will recognize credentials with `<REGISTRY_NAME>` `index.docker.io`,
but the Docker CLI will not.</sub>
!!! important "Using a private registry on a local host"
To use a private registry hosted locally, make sure to correctly specify the registry host
in both `config.json` and the `docker run` command or `docker-compose` file.
Valid hosts are `localhost[:PORT]`, `HOST:PORT`,
or any multi-part `domain.name` or IP-address with or without a port.
Examples:
* `localhost` -> `localhost/myimage`
* `127.0.0.1` -> `127.0.0.1/myimage:mytag`
* `host.domain` -> `host.domain/myorganization/myimage`
* `other-lan-host:80` -> `other-lan-host:80/imagename:latest`
The required `auth` string can be generated as follows:
```bash
echo -n 'username:password' | base64
```
!!! info "Username and Password for GCloud"
For gcloud, we'll use `_json_key` as our username and the content of `gcloudauth.json` as the password.
```
bash echo -n "_json_key:$(cat gcloudauth.json)" | base64 -w0
```
When the watchtower Docker container is started, the created configuration file
(`<PATH>/config.json` in this example) needs to be passed to the container:
```bash
docker run [...] -v <PATH>/config.json:/config.json containrrr/watchtower
```
### Share the Docker configuration file
To pull an image from a private registry, `docker login` needs to be called first, to get access
to the registry. The provided credentials are stored in a configuration file called `<PATH_TO_HOME_DIR>/.docker/config.json`.
This configuration file can be directly used by watchtower. In this case, the creation of an
additional configuration file is not necessary.
When the Docker container is started, pass the configuration file to watchtower:
```bash
docker run [...] -v <PATH_TO_HOME_DIR>/.docker/config.json:/config.json containrrr/watchtower
```
When creating the watchtower container via docker-compose, use the following lines:
```yaml
version: "3.4"
services:
watchtower:
image: containrrr/watchtower:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- <PATH_TO_HOME_DIR>/.docker/config.json:/config.json
...
```
#### Docker Config path
By default, watchtower will look for the `config.json` file in `/`, but this can be changed by setting the `DOCKER_CONFIG` environment variable to the directory path where your config is located. This is useful for setups where the config.json file is changed while the watchtower instance is running, as the changes will not be picked up for a mounted file if the inode changes.
Example usage:
```yaml
version: "3.4"
services:
watchtower:
image: containrrr/watchtower
environment:
DOCKER_CONFIG: /config
volumes:
- /etc/watchtower/config/:/config/
- /var/run/docker.sock:/var/run/docker.sock
```
## Credential helpers
Some private Docker registries (the most prominent probably being AWS ECR) use non-standard ways of authentication.
To be able to use this together with watchtower, we need to use a credential helper.
To keep the image size small we've decided to not include any helpers in the watchtower image, instead we'll put the
helper in a separate container and mount it using volumes.
### Example
Example implementation for use with [amazon-ecr-credential-helper](https://github.com/awslabs/amazon-ecr-credential-helper):
Use the dockerfile below to build the [amazon-ecr-credential-helper](https://github.com/awslabs/amazon-ecr-credential-helper),
in a volume that may be mounted onto your watchtower container.
1. Create the Dockerfile (contents below):
```Dockerfile
FROM golang:1.20
ENV GO111MODULE off
ENV CGO_ENABLED 0
ENV REPO github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login
RUN go get -u $REPO
RUN rm /go/bin/docker-credential-ecr-login
RUN go build \
-o /go/bin/docker-credential-ecr-login \
/go/src/$REPO
WORKDIR /go/bin/
```
2. Use the following commands to build the aws-ecr-dock-cred-helper and store it's output in a volume:
```bash
# Create a volume to store the command (once built)
docker volume create helper
# Build the container
docker build -t aws-ecr-dock-cred-helper .
# Build the command and store it in the new volume in the /go/bin directory.
docker run -d --rm --name aws-cred-helper \
--volume helper:/go/bin aws-ecr-dock-cred-helper
```
3. Create a configuration file for docker, and store it in $HOME/.docker/config.json (replace the <AWS_ACCOUNT_ID>
placeholders with your AWS Account ID and <AWS_ECR_REGION> with your AWS ECR Region):
```json
{
"credsStore" : "ecr-login",
"HttpHeaders" : {
"User-Agent" : "Docker-Client/19.03.1 (XXXXXX)"
},
"auths" : {
"<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_ECR_REGION>.amazonaws.com" : {}
},
"credHelpers": {
"<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_ECR_REGION>.amazonaws.com" : "ecr-login"
}
}
```
4. Create a docker-compose file (as an example) to help launch the container:
```yaml
version: "3.4"
services:
# Check for new images and restart things if a new image exists
# for any of our containers.
watchtower:
image: containrrr/watchtower:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- .docker/config.json:/config.json
- helper:/go/bin
environment:
- HOME=/
- PATH=$PATH:/go/bin
- AWS_REGION=us-west-1
volumes:
helper:
external: true
```
A few additional notes:
1. With docker-compose the volume (helper, in this case) MUST be set to `external: true`, otherwise docker-compose
will preface it with the directory name.
2. Note that "credsStore" : "ecr-login" is needed - and in theory if you have that you can remove the
credHelpers section
3. I have this running on an EC2 instance that has credentials assigned to it - so no keys are needed; however,
you may need to include the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables as well.
4. An alternative to adding the various variables is to create a ~/.aws/config and ~/.aws/credentials files and
place the settings there, then mount the ~/.aws directory to / in the container.
================================================
FILE: docs/remote-hosts.md
================================================
By default, watchtower is set-up to monitor the local Docker daemon (the same daemon running the watchtower container itself). However, it is possible to configure watchtower to monitor a remote Docker endpoint. When starting the watchtower container you can specify a remote Docker endpoint with either the `--host` flag or the `DOCKER_HOST` environment variable:
```bash
docker run -d \
--name watchtower \
containrrr/watchtower --host "tcp://10.0.1.2:2375"
```
or
```bash
docker run -d \
--name watchtower \
-e DOCKER_HOST="tcp://10.0.1.2:2375" \
containrrr/watchtower
```
Note in both of the examples above that it is unnecessary to mount the _/var/run/docker.sock_ into the watchtower container.
================================================
FILE: docs/running-multiple-instances.md
================================================
By default, Watchtower will clean up other instances and won't allow multiple instances running on the same Docker host or swarm. It is possible to override this behavior by defining a [scope](https://containrrr.github.io/watchtower/arguments/#filter_by_scope) to each running instance.
!!! note
- Multiple instances can't run with the same scope;
- An instance without a scope will clean up other running instances, even if they have a defined scope;
- Supplying `none` as the scope will treat `com.centurylinklabs.watchtower.scope=none`, `com.centurylinklabs.watchtower.scope=` and the lack of a `com.centurylinklabs.watchtower.scope` label as the scope `none`. This effectly enables you to run both scoped and unscoped watchtower instances on the same machine.
To define an instance monitoring scope, use the `--scope` argument or the `WATCHTOWER_SCOPE` environment variable on startup and set the `com.centurylinklabs.watchtower.scope` label with the same value for the containers you want to include in this instance's scope (including the instance itself).
For example, in a Docker Compose config file:
```yaml
version: '3'
services:
app-with-scope:
image: myapps/monitored-by-watchtower
labels: [ "com.centurylinklabs.watchtower.scope=myscope" ]
scoped-watchtower:
image: containrrr/watchtower
volumes: [ "/var/run/docker.sock:/var/run/docker.sock" ]
command: --interval 30 --scope myscope
labels: [ "com.centurylinklabs.watchtower.scope=myscope" ]
unscoped-app-a:
image: myapps/app-a
unscoped-app-b:
image: myapps/app-b
labels: [ "com.centurylinklabs.watchtower.scope=none" ]
unscoped-app-c:
image: myapps/app-b
labels: [ "com.centurylinklabs.watchtower.scope=" ]
unscoped-watchtower:
image: containrrr/watchtower
volumes: [ "/var/run/docker.sock:/var/run/docker.sock" ]
command: --interval 30 --scope none
```
================================================
FILE: docs/secure-connections.md
================================================
Watchtower is also capable of connecting to Docker endpoints which are protected by SSL/TLS. If you've used _docker-machine_ to provision your remote Docker host, you simply need to volume mount the certificates generated by _docker-machine_ into the watchtower container and optionally specify `--tlsverify` flag.
The _docker-machine_ certificates for a particular host can be located by executing the `docker-machine env` command for the desired host (note the values for the `DOCKER_HOST` and `DOCKER_CERT_PATH` environment variables that are returned from this command). The directory containing the certificates for the remote host needs to be mounted into the watchtower container at _/etc/ssl/docker_.
With the certificates mounted into the watchtower container you need to specify the `--tlsverify` flag to enable verification of the certificate:
```bash
docker run -d \
--name watchtower \
-e DOCKER_HOST=$DOCKER_HOST \
-e DOCKER_CERT_PATH=/etc/ssl/docker \
-v $DOCKER_CERT_PATH:/etc/ssl/docker \
containrrr/watchtower --tlsverify
```
================================================
FILE: docs/stop-signals.md
================================================
When watchtower detects that a running container needs to be updated it will stop the container by sending it a SIGTERM signal.
If your container should be shutdown with a different signal you can communicate this to watchtower by setting a label named _com.centurylinklabs.watchtower.stop-signal_ with the value of the desired signal.
This label can be coded directly into your image by using the `LABEL` instruction in your Dockerfile:
```docker
LABEL com.centurylinklabs.watchtower.stop-signal="SIGHUP"
```
Or, it can be specified as part of the `docker run` command line:
```bash
docker run -d --label=com.centurylinklabs.watchtower.stop-signal=SIGHUP someimage
```
================================================
FILE: docs/stylesheets/theme.css
================================================
[data-md-color-scheme="containrrr"] {
/* Primary and accent */
--md-primary-fg-color: #406170;
--md-primary-fg-color--light:#acbfc7;
--md-primary-fg-color--dark: #003343;
--md-accent-fg-color: #003343;
--md-accent-fg-color--transparent: #00334310;
/* Typeset overrides */
--md-typeset-a-color: var(--md-primary-fg-color);
}
[data-md-color-scheme="containrrr-dark"] {
--md-hue: 199;
/* Primary and accent */
--md-primary-fg-color: hsl(199deg 27% 35% / 100%);
--md-primary-fg-color--link: hsl(199deg 45% 65% / 100%);
--md-primary-fg-color--light: hsl(198deg 19% 73% / 100%);
--md-primary-fg-color--dark: hsl(194deg 100% 13% / 100%);
--md-accent-fg-color: hsl(194deg 45% 50% / 100%);
--md-accent-fg-color--transparent: hsl(194deg 45% 50% / 6.3%);
/* Default */
--md-default-fg-color: hsl(var(--md-hue) 75% 95% / 100%);
--md-default-fg-color--light: hsl(var(--md-hue) 75% 90% / 62%);
--md-default-fg-color--lighter: hsl(var(--md-hue) 75% 90% / 32%);
--md-default-fg-color--lightest: hsl(var(--md-hue) 75% 90% / 12%);
--md-default-bg-color: hsl(var(--md-hue) 15% 21% / 100%);
--md-default-bg-color--light: hsl(var(--md-hue) 15% 21% / 54%);
--md-default-bg-color--lighter: hsl(var(--md-hue) 15% 21% / 26%);
--md-default-bg-color--lightest: hsl(var(--md-hue) 15% 21% / 7%);
/* Code */
--md-code-fg-color: hsl(var(--md-hue) 18% 86% / 100%);
--md-code-bg-color: hsl(var(--md-hue) 15% 15% / 100%);
--md-code-hl-color: hsl(218deg 100% 63% / 15%);
--md-code-hl-number-color: hsl(346deg 74% 63% / 100%);
--md-code-hl-special-color: hsl(320deg 83% 66% / 100%);
--md-code-hl-function-color: hsl(271deg 57% 65% / 100%);
--md-code-hl-constant-color: hsl(230deg 62% 70% / 100%);
--md-code-hl-keyword-color: hsl(199deg 33% 64% / 100%);
--md-code-hl-string-color: hsl( 50deg 34% 74% / 100%);
--md-code-hl-name-color: var(--md-code-fg-color);
--md-code-hl-operator-color: var(--md-default-fg-color--light);
--md-code-hl-punctuation-color: var(--md-default-fg-color--light);
--md-code-hl-comment-color: var(--md-default-fg-color--light);
--md-code-hl-generic-color: var(--md-default-fg-color--light);
--md-code-hl-variable-color: hsl(241deg 22% 60% / 100%);
/* Typeset */
--md-typeset-color: var(--md-default-fg-color);
--md-typeset-a-color: var(--md-primary-fg-color--link);
--md-typeset-mark-color: hsl(218deg 100% 63% / 30%);
--md-typeset-kbd-color: hsl(var(--md-hue) 15% 94% / 12%);
--md-typeset-kbd-accent-color: hsl(var(--md-hue) 15% 94% / 20%);
--md-typeset-kbd-border-color: hsl(var(--md-hue) 15% 14% / 100%);
--md-typeset-table-color: hsl(var(--md-hue) 75% 95% / 12%);
/* Admonition */
--md-admonition-fg-color: var(--md-default-fg-color);
--md-admonition-bg-color: var(--md-default-bg-color);
/* Footer */
--md-footer-bg-color: hsl(var(--md-hue) 15% 12% / 87%);
--md-footer-bg-color--dark: hsl(var(--md-hue) 15% 10% / 100%);
/* Shadows */
--md-shadow-z1:
0 0.2rem 0.50rem rgba(0 0 0 20%),
0 0 0.05rem rgba(0 0 0 10%);
--md-shadow-z2:
0 0.2rem 0.50rem rgba(0 0 0 30%),
0 0 0.05rem rgba(0 0 0 25%);
--md-shadow-z3:
0 0.2rem 0.50rem rgba(0 0 0 40%),
0 0 0.05rem rgba(0 0 0 35%);
}
.md-header-nav__button.md-logo {
padding: 0;
}
.md-header-nav__button.md-logo img {
width: 1.6rem;
height: 1.6rem;
}
================================================
FILE: docs/template-preview.md
================================================
<style>
#tplprev {
margin: 0;
display: flex;
flex-direction: column;
row-gap: 1rem;
box-sizing: border-box;
position: relative;
margin-right: -13.3rem
}
#tplprev textarea {
box-decoration-break: slice;
overflow: auto;
padding: 0.77em 1.18em;
scrollbar-color: var(--md-default-fg-color--lighter) transparent;
scrollbar-width: thin;
touch-action: auto;
word-break: normal;
height: 420px;
flex: 1;
}
#tplprev .controls {
display: flex;
flex-direction: row;
column-gap: 0.5rem
}
#tplprev textarea, #tplprev input {
background-color: var(--md-code-bg-color);
border-width: 0;
border-radius: 0.1rem;
color: var(--md-code-fg-color);
font-feature-settings: "kern";
font-family: var(--md-code-font-family);
}
.numfield {
font-size: .7rem;
display: flex;
flex-direction: column;
justify-content: space-between;
}
#tplprev button {
border-radius: 0.1rem;
color: var(--md-primary-bg-color);
background-color: var(--md-primary-fg-color);
flex:1;
min-width: 12ch;
padding: 0.5rem
}
#tplprev button:hover {
background-color: var(--md-accent-fg-color);
}
#tplprev input[type="number"] { width: 5ch; flex: 1; font-size: 1rem; }
#tplprev fieldset {
margin-top: -0.5rem;
display: flex;
flex: 1;
column-gap: 0.5rem;
}
#tplprev .template-wrapper {
display: flex;
flex:1;
column-gap: 1rem;
}
#tplprev .result-wrapper {
flex: 1;
display: flex
}
#result {
font-size: 0.7rem;
background-color: var(--md-code-bg-color);
scrollbar-color: var(--md-default-fg-color--lighter) transparent;
scrollbar-width: thin;
touch-action: auto;
overflow: auto;
padding: 0.77em 1.18em;
margin:0;
height: 540px;
flex:1;
width:100%
}
#result b {color: var(--md-code-hl-special-color)}
#result i {color: var(--md-code-hl-keyword-color)}
#tplprev .loading {
position: absolute;
inset: 0;
display: flex;
padding: 1rem;
box-sizing: border-box;
background: var(--md-code-bg-color);
margin-top: 0
}
</style>
<script src="../assets/wasm_exec.js"></script>
<script>
let wasmLoaded = false;
const updatePreview = () => {
if (!wasmLoaded) return;
const form = document.querySelector('#tplprev');
const input = form.template.value;
console.log('Input: %o', input);
const arrFromCount = (key) => Array.from(Array(form[key]?.valueAsNumber ?? 0), () => key);
const states = form.report.value === "yes" ? [
...arrFromCount("skipped"),
...arrFromCount("scanned"),
...arrFromCount("updated"),
...arrFromCount("failed" ),
...arrFromCount("fresh" ),
...arrFromCount("stale" ),
] : [];
console.log("States: %o", states);
const levels = form.log.value === "yes" ? [
...arrFromCount("error"),
...arrFromCount("warning"),
...arrFromCount("info"),
...arrFromCount("debug"),
] : [];
console.log("Levels: %o", levels);
const output = WATCHTOWER.tplprev(input, states, levels);
console.log('Output: \n%o', output);
if (output.startsWith('Error: ')) {
document.querySelector('#result').innerHTML = `<b>Error</b>: ${output.substring(7)}`;
} else if (output.length) {
document.querySelector('#result').innerText = output;
} else {
document.querySelector('#result').innerHTML = '<i>empty (would not be sent as a notification)</i>';
}
}
const formSubmitted = (e) => {
//e.preventDefault();
//updatePreview();
}
let debounce;
const inputUpdated = () => {
if(debounce) clearTimeout(debounce);
debounce = setTimeout(() => updatePreview(), 400);
}
const formChanged = (e) => {
console.log('form changed: %o', e);
const targetToggle = e.target.dataset['toggle'];
if (targetToggle) {
e.target.form[targetToggle].value = e.target.checked ? "yes" : "no";
}
updatePreview()
}
const go = new Go();
WebAssembly.instantiateStreaming(fetch("../assets/tplprev.wasm"), go.importObject).then((result) => {
go.run(result.instance);
document.querySelector('#tplprev .loading').style.display = "none";
wasmLoaded = true;
updatePreview();
});
</script>
<form id="tplprev" onchange="formChanged(event)" onsubmit="formSubmitted(event)">
<pre class="loading">loading wasm...</pre>
<div class="template-wrapper">
<textarea name="template" type="text" onkeyup="inputUpdated()">{{- with .Report -}}
{{- if ( or .Updated .Failed ) -}}
{{len .Scanned}} Scanned, {{len .Updated}} Updated, {{len .Failed}} Failed
{{- range .Updated}}
- {{.Name}} ({{.ImageName}}): {{.CurrentImageID.ShortID}} updated to {{.LatestImageID.ShortID}}
{{- end -}}
{{- range .Fresh}}
- {{.Name}} ({{.ImageName}}): {{.State}}
{{- end -}}
{{- range .Skipped}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- range .Failed}}
- {{.Name}} ({{.ImageName}}): {{.State}}: {{.Error}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if (and .Entries .Report) }}
Logs:
{{ end -}}
{{range .Entries -}}{{.Time.Format "2006-01-02T15:04:05Z07:00"}} [{{.Level}}] {{.Message}}{{"\n"}}{{- end -}}</textarea>
</div>
<div class="controls">
<fieldset>
<input type="hidden" name="report" value="yes" />
<legend><label><input type="checkbox" data-toggle="report" checked /> Container report</label></legend>
<label class="numfield">
Skipped:
<input type="number" name="skipped" value="3" />
</label>
<label class="numfield">
Scanned:
<input type="number" name="scanned" value="3" />
</label>
<label class="numfield">
Updated:
<input type="number" name="updated" value="3" />
</label>
<label class="numfield">
Failed:
<input type="number" name="failed" value="3" />
</label>
<label class="numfield">
Fresh:
<input type="number" name="fresh" value="3" />
</label>
<label class="numfield">
Stale:
<input type="number" name="stale" value="3" />
</label>
</fieldset>
<fieldset>
<input type="hidden" name="log" value="yes" />
<legend><label><input type="checkbox" data-toggle="log" checked /> Log entries</label></legend>
<label class="numfield">
Error:
<input type="number" name="error" value="1" />
</label>
<label class="numfield">
Warning:
<input type="number" name="warning" value="2" />
</label>
<label class="numfield">
Info:
<input type="number" name="info" value="3" />
</label>
<label class="numfield">
Debug:
<input type="number" name="debug" value="4" />
</label>
</fieldset>
<button type="submit">Update preview</button>
</div>
<div style="result-wrapper">
<pre id="result"></pre>
</div>
</form>
<script>
const loadQueryVals = () => {
const form = document.querySelector('#tplprev');
const params = new URLSearchParams(location.search);
for(const [key, value] of params){
form[key].value = value;
const toggleInput = form.querySelector(`[data-toggle="${key}"]`);
if (toggleInput) {
toggleInput.checked = value === "yes";
}
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadQueryVals());
} else {
loadQueryVals();
}
</script>
================================================
FILE: docs/updating.md
================================================
## Updating Watchtower
If watchtower is monitoring the same Docker daemon under which the watchtower container itself is running (i.e. if you
volume-mounted `/var/run/docker.sock` into the watchtower container) then it has the ability to update itself.
If a new version of the `containrrr/watchtower` image is pushed to the Docker Hub, your watchtower will pull down the
new image and restart itself automatically.
================================================
FILE: docs/usage-overview.md
================================================
Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `containrrr/watchtower` image. If you are using ARM based architecture, pull the appropriate `containrrr/watchtower:armhf-<tag>` image from the [containrrr Docker Hub](https://hub.docker.com/r/containrrr/watchtower/tags/).
Since the watchtower code needs to interact with the Docker API in order to monitor the running containers, you need to mount _/var/run/docker.sock_ into the container with the `-v` flag when you run it.
Run the `watchtower` container with the following command:
```bash
docker run -d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower
```
If pulling images from private Docker registries, supply registry authentication credentials with the environment variables `REPO_USER` and `REPO_PASS`
or by mounting the host's docker config file into the container (at the root of the container filesystem `/`).
Passing environment variables:
```bash
docker run -d \
--name watchtower \
-e REPO_USER=username \
-e REPO_PASS=password \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower container_to_watch --debug
```
Also check out [this Stack Overflow answer](https://stackoverflow.com/a/30494145/7872793) for more options on how to pass environment variables.
Alternatively if you 2FA authentication setup on Docker Hub then passing username and password will be insufficient. Instead you can run `docker login` to store your credentials in `$HOME/.docker/config.json` and then mount this config file to make it available to the Watchtower container:
```bash
docker run -d \
--name watchtower \
-v $HOME/.docker/config.json:/config.json \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower container_to_watch --debug
```
!!! note "Changes to config.json while running"
If you mount `config.json` in the manner above, changes from the host system will (generally) not be propagated to the
running container. Mounting files into the Docker daemon uses bind mounts, which are based on inodes. Most
applications (including `docker login` and `vim`) will not directly edit the file, but instead make a copy and replace
the original file, which results in a new inode which in turn _breaks_ the bind mount.
**As a workaround**, you can create a symlink to your `config.json` file and then mount the symlink in the container.
The symlinked file will always have the same inode, which keeps the bind mount intact and will ensure changes
to the original file are propagated to the running container (regardless of the inode of the source file!).
If you mount the config file as described above, be sure to also prepend the URL for the registry when starting up your
watched image (you can omit the https://). Here is a complete docker-compose.yml file that starts up a docker container
from a private repo on the GitHub Registry and monitors it with watchtower. Note the command argument changing the interval
to 30s rather than the default 24 hours.
```yaml
version: "3"
services:
cavo:
image: ghcr.io/<org>/<image>:<tag>
ports:
- "443:3443"
- "80:3080"
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /root/.docker/config.json:/config.json
command: --interval 30
```
================================================
FILE: docs-requirements.txt
================================================
mkdocs
mkdocs-material
md-toc
================================================
FILE: go.mod
================================================
module github.com/containrrr/watchtower
go 1.20
require (
github.com/containrrr/shoutrrr v0.8.0
github.com/distribution/reference v0.5.0
github.com/docker/cli v24.0.7+incompatible
github.com/docker/docker v24.0.7+incompatible
github.com/docker/go-connections v0.4.0
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.30.0
github.com/prometheus/client_golang v1.18.0
github.com/robfig/cron v1.2.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.8.4
golang.org/x/net v0.19.0
)
require github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.1 // indirect
github.com/docker/go-units v0.4.0 // indi
gitextract_hq806g1o/
├── .all-contributorsrc
├── .codacy.yml
├── .devbots/
│ └── lock-issue.yml
├── .editorconfig
├── .github/
│ ├── CODEOWNERS
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug.yml
│ │ ├── config.yml
│ │ └── feature_request.yml
│ ├── dependabot.yml
│ ├── pull_request_template.md
│ ├── stale.yml
│ └── workflows/
│ ├── codeql-analysis.yml
│ ├── dependabot-approve.yml
│ ├── greetings.yml
│ ├── publish-docs.yml
│ ├── pull-request.yml
│ ├── release-dev.yaml
│ └── release.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── SECURITY.md
├── build.sh
├── cmd/
│ ├── notify-upgrade.go
│ └── root.go
├── code_of_conduct.md
├── docker-compose.yml
├── dockerfiles/
│ ├── Dockerfile
│ ├── Dockerfile.dev-self-contained
│ ├── Dockerfile.self-contained
│ └── container-networking/
│ └── docker-compose.yml
├── docs/
│ ├── arguments.md
│ ├── container-selection.md
│ ├── http-api-mode.md
│ ├── index.md
│ ├── introduction.md
│ ├── lifecycle-hooks.md
│ ├── linked-containers.md
│ ├── metrics.md
│ ├── notifications.md
│ ├── private-registries.md
│ ├── remote-hosts.md
│ ├── running-multiple-instances.md
│ ├── secure-connections.md
│ ├── stop-signals.md
│ ├── stylesheets/
│ │ └── theme.css
│ ├── template-preview.md
│ ├── updating.md
│ └── usage-overview.md
├── docs-requirements.txt
├── go.mod
├── go.sum
├── goreleaser.yml
├── grafana/
│ ├── dashboards/
│ │ ├── dashboard.json
│ │ └── dashboard.yml
│ └── datasources/
│ └── datasource.yml
├── internal/
│ ├── actions/
│ │ ├── actions_suite_test.go
│ │ ├── check.go
│ │ ├── mocks/
│ │ │ ├── client.go
│ │ │ ├── container.go
│ │ │ └── progress.go
│ │ ├── update.go
│ │ └── update_test.go
│ ├── flags/
│ │ ├── flags.go
│ │ └── flags_test.go
│ ├── meta/
│ │ └── meta.go
│ └── util/
│ ├── rand_name.go
│ ├── rand_sha256.go
│ ├── util.go
│ └── util_test.go
├── main.go
├── mkdocs.yml
├── oryxBuildBinary
├── pkg/
│ ├── api/
│ │ ├── api.go
│ │ ├── api_test.go
│ │ ├── metrics/
│ │ │ ├── metrics.go
│ │ │ └── metrics_test.go
│ │ └── update/
│ │ └── update.go
│ ├── container/
│ │ ├── cgroup_id.go
│ │ ├── cgroup_id_test.go
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── container.go
│ │ ├── container_mock_test.go
│ │ ├── container_suite_test.go
│ │ ├── container_test.go
│ │ ├── errors.go
│ │ ├── metadata.go
│ │ ├── mocks/
│ │ │ ├── ApiServer.go
│ │ │ ├── FilterableContainer.go
│ │ │ ├── container_ref.go
│ │ │ └── data/
│ │ │ ├── container_net_consumer-missing_supplier.json
│ │ │ ├── container_net_consumer.json
│ │ │ ├── container_net_supplier.json
│ │ │ ├── container_restarting.json
│ │ │ ├── container_running.json
│ │ │ ├── container_stopped.json
│ │ │ ├── container_watchtower.json
│ │ │ ├── containers.json
│ │ │ ├── image_default.json
│ │ │ ├── image_net_consumer.json
│ │ │ ├── image_net_producer.json
│ │ │ └── image_running.json
│ │ └── util_test.go
│ ├── filters/
│ │ ├── filters.go
│ │ └── filters_test.go
│ ├── lifecycle/
│ │ └── lifecycle.go
│ ├── metrics/
│ │ └── metrics.go
│ ├── notifications/
│ │ ├── common_templates.go
│ │ ├── email.go
│ │ ├── gotify.go
│ │ ├── json.go
│ │ ├── json_test.go
│ │ ├── model.go
│ │ ├── msteams.go
│ │ ├── notifications_suite_test.go
│ │ ├── notifier.go
│ │ ├── notifier_test.go
│ │ ├── preview/
│ │ │ ├── data/
│ │ │ │ ├── data.go
│ │ │ │ ├── logs.go
│ │ │ │ ├── preview_strings.go
│ │ │ │ ├── report.go
│ │ │ │ └── status.go
│ │ │ └── tplprev.go
│ │ ├── shoutrrr.go
│ │ ├── shoutrrr_test.go
│ │ ├── slack.go
│ │ └── templates/
│ │ └── funcs.go
│ ├── registry/
│ │ ├── auth/
│ │ │ ├── auth.go
│ │ │ └── auth_test.go
│ │ ├── digest/
│ │ │ ├── digest.go
│ │ │ └── digest_test.go
│ │ ├── helpers/
│ │ │ ├── helpers.go
│ │ │ └── helpers_test.go
│ │ ├── manifest/
│ │ │ ├── manifest.go
│ │ │ └── manifest_test.go
│ │ ├── registry.go
│ │ ├── registry_suite_test.go
│ │ ├── registry_test.go
│ │ ├── trust.go
│ │ └── trust_test.go
│ ├── session/
│ │ ├── container_status.go
│ │ ├── progress.go
│ │ └── report.go
│ ├── sorter/
│ │ └── sort.go
│ └── types/
│ ├── container.go
│ ├── convertible_notifier.go
│ ├── filter.go
│ ├── filterable_container.go
│ ├── notifier.go
│ ├── registry_credentials.go
│ ├── report.go
│ ├── token_response.go
│ └── update_params.go
├── prometheus/
│ └── prometheus.yml
├── scripts/
│ ├── build-tplprev.sh
│ ├── codecov.sh
│ ├── contnet-tests.sh
│ ├── dependency-test.sh
│ ├── docker-util.sh
│ ├── du-cli.sh
│ └── lifecycle-tests.sh
└── tplprev/
├── main.go
└── main_wasm.go
SYMBOL INDEX (500 symbols across 81 files)
FILE: cmd/notify-upgrade.go
function NewNotifyUpgradeCommand (line 21) | func NewNotifyUpgradeCommand() *cobra.Command {
function runNotifyUpgrade (line 29) | func runNotifyUpgrade(cmd *cobra.Command, args []string) {
function runNotifyUpgradeE (line 35) | func runNotifyUpgradeE(cmd *cobra.Command, _ []string) error {
function tryOrLog (line 103) | func tryOrLog(err error, message string) {
function logf (line 109) | func logf(format string, v ...interface{}) {
FILE: cmd/root.go
function NewRootCommand (line 51) | func NewRootCommand() *cobra.Command {
function init (line 65) | func init() {
function Execute (line 73) | func Execute() {
function PreRun (line 81) | func PreRun(cmd *cobra.Command, _ []string) {
function Run (line 138) | func Run(c *cobra.Command, names []string) {
function logNotifyExit (line 213) | func logNotifyExit(err error) {
function awaitDockerClient (line 219) | func awaitDockerClient() {
function formatDuration (line 224) | func formatDuration(d time.Duration) string {
function writeStartupMessage (line 263) | func writeStartupMessage(c *cobra.Command, sched time.Time, filtering st...
function runUpgradesOnSchedule (line 312) | func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering ...
function runUpdatesWithNotifications (line 359) | func runUpdatesWithNotifications(filter t.Filter) *metrics.Metric {
FILE: internal/actions/actions_suite_test.go
function TestActions (line 17) | func TestActions(t *testing.T) {
FILE: internal/actions/check.go
function CheckForSanity (line 17) | func CheckForSanity(client container.Client, filter types.Filter, rollin...
function CheckForMultipleWatchtowerInstances (line 41) | func CheckForMultipleWatchtowerInstances(client container.Client, cleanu...
function cleanupExcessWatchtowers (line 61) | func cleanupExcessWatchtowers(containers []types.Container, client conta...
FILE: internal/actions/mocks/client.go
type MockClient (line 12) | type MockClient struct
method ListContainers (line 41) | func (client MockClient) ListContainers(_ t.Filter) ([]t.Container, er...
method StopContainer (line 46) | func (client MockClient) StopContainer(c t.Container, _ time.Duration)...
method StartContainer (line 54) | func (client MockClient) StartContainer(_ t.Container) (t.ContainerID,...
method RenameContainer (line 59) | func (client MockClient) RenameContainer(_ t.Container, _ string) error {
method RemoveImageByID (line 64) | func (client MockClient) RemoveImageByID(_ t.ImageID) error {
method GetContainer (line 70) | func (client MockClient) GetContainer(_ t.ContainerID) (t.Container, e...
method ExecuteCommand (line 75) | func (client MockClient) ExecuteCommand(_ t.ContainerID, command strin...
method IsContainerStale (line 89) | func (client MockClient) IsContainerStale(cont t.Container, params t.U...
method WarnOnHeadPullFailed (line 98) | func (client MockClient) WarnOnHeadPullFailed(_ t.Container) bool {
type TestData (line 19) | type TestData struct
method TriedToRemoveImage (line 27) | func (testdata *TestData) TriedToRemoveImage() bool {
function CreateMockClient (line 32) | func CreateMockClient(data *TestData, pullImages bool, removeVolumes boo...
FILE: internal/actions/mocks/container.go
function CreateMockContainer (line 17) | func CreateMockContainer(id string, name string, image string, created t...
function CreateMockImageInfo (line 41) | func CreateMockImageInfo(image string) *types.ImageInspect {
function CreateMockContainerWithImageInfo (line 51) | func CreateMockContainerWithImageInfo(id string, name string, image stri...
function CreateMockContainerWithImageInfoP (line 56) | func CreateMockContainerWithImageInfoP(id string, name string, image str...
function CreateMockContainerWithDigest (line 76) | func CreateMockContainerWithDigest(id string, name string, image string,...
function CreateMockContainerWithConfig (line 83) | func CreateMockContainerWithConfig(id string, name string, image string,...
function CreateContainerForProgress (line 107) | func CreateContainerForProgress(index int, idPrefix int, nameFormat stri...
function CreateMockContainerWithLinks (line 123) | func CreateMockContainerWithLinks(id string, name string, image string, ...
FILE: internal/actions/mocks/progress.go
function CreateMockProgressReport (line 12) | func CreateMockProgressReport(states ...session.State) wt.Report {
FILE: internal/actions/update.go
function Update (line 19) | func Update(client container.Client, params types.UpdateParams) (types.R...
function performRollingRestart (line 97) | func performRollingRestart(containers []types.Container, client containe...
function stopContainersInReversedOrder (line 123) | func stopContainersInReversedOrder(containers []types.Container, client ...
function stopStaleContainer (line 138) | func stopStaleContainer(container types.Container, client container.Clie...
function restartContainersInSortedOrder (line 175) | func restartContainersInSortedOrder(containers []types.Container, client...
function cleanupImages (line 200) | func cleanupImages(client container.Client, imageIDs map[types.ImageID]b...
function restartStaleContainer (line 211) | func restartStaleContainer(container types.Container, client container.C...
function UpdateImplicitRestart (line 236) | func UpdateImplicitRestart(containers []types.Container) {
function linkedContainerMarkedForRestart (line 258) | func linkedContainerMarkedForRestart(links []string, containers []types....
FILE: internal/actions/update_test.go
function getCommonTestData (line 17) | func getCommonTestData(keepContainer string) *TestData {
function getLinkedTestData (line 40) | func getLinkedTestData(withImageInfo bool) *TestData {
FILE: internal/flags/flags.go
constant DockerAPIMinVersion (line 20) | DockerAPIMinVersion string = "1.25"
function RegisterDockerFlags (line 25) | func RegisterDockerFlags(rootCmd *cobra.Command) {
function RegisterSystemFlags (line 33) | func RegisterSystemFlags(rootCmd *cobra.Command) {
function RegisterNotificationFlags (line 217) | func RegisterNotificationFlags(rootCmd *cobra.Command) {
function envString (line 394) | func envString(key string) string {
function envStringSlice (line 399) | func envStringSlice(key string) []string {
function envInt (line 404) | func envInt(key string) int {
function envBool (line 409) | func envBool(key string) bool {
function envDuration (line 414) | func envDuration(key string) time.Duration {
function SetDefaults (line 420) | func SetDefaults() {
function EnvConfig (line 437) | func EnvConfig(cmd *cobra.Command) error {
function ReadFlags (line 467) | func ReadFlags(cmd *cobra.Command) (bool, bool, bool, time.Duration) {
function setEnvOptStr (line 492) | func setEnvOptStr(env string, opt string) error {
function setEnvOptBool (line 503) | func setEnvOptBool(env string, opt bool) error {
function GetSecretsFromFiles (line 512) | func GetSecretsFromFiles(rootCmd *cobra.Command) {
function getSecretFromFile (line 531) | func getSecretFromFile(flags *pflag.FlagSet, secret string) error {
function isFile (line 572) | func isFile(s string) bool {
function ProcessFlagAliases (line 585) | func ProcessFlagAliases(flags *pflag.FlagSet) {
function SetupLogging (line 636) | func SetupLogging(f *pflag.FlagSet) error {
function flagIsEnabled (line 675) | func flagIsEnabled(flags *pflag.FlagSet, name string) bool {
function appendFlagValue (line 683) | func appendFlagValue(flags *pflag.FlagSet, name string, values ...string...
function setFlagIfDefault (line 700) | func setFlagIfDefault(flags *pflag.FlagSet, name string, value string) {
FILE: internal/flags/flags_test.go
function TestEnvConfig_Defaults (line 16) | func TestEnvConfig_Defaults(t *testing.T) {
function TestEnvConfig_Custom (line 34) | func TestEnvConfig_Custom(t *testing.T) {
function TestGetSecretsFromFilesWithString (line 51) | func TestGetSecretsFromFilesWithString(t *testing.T) {
function TestGetSecretsFromFilesWithFile (line 58) | func TestGetSecretsFromFilesWithFile(t *testing.T) {
function TestGetSliceSecretsFromFiles (line 75) | func TestGetSliceSecretsFromFiles(t *testing.T) {
function testGetSecretsFromFiles (line 94) | func testGetSecretsFromFiles(t *testing.T, flagName string, expected str...
function TestHTTPAPIPeriodicPollsFlag (line 108) | func TestHTTPAPIPeriodicPollsFlag(t *testing.T) {
function TestIsFile (line 123) | func TestIsFile(t *testing.T) {
function TestProcessFlagAliases (line 128) | func TestProcessFlagAliases(t *testing.T) {
function TestProcessFlagAliasesLogLevelFromEnvironment (line 163) | func TestProcessFlagAliasesLogLevelFromEnvironment(t *testing.T) {
function TestLogFormatFlag (line 180) | func TestLogFormatFlag(t *testing.T) {
function TestLogLevelFlag (line 219) | func TestLogLevelFlag(t *testing.T) {
function TestProcessFlagAliasesSchedAndInterval (line 231) | func TestProcessFlagAliasesSchedAndInterval(t *testing.T) {
function TestProcessFlagAliasesScheduleFromEnvironment (line 247) | func TestProcessFlagAliasesScheduleFromEnvironment(t *testing.T) {
function TestProcessFlagAliasesInvalidPorcelaineVersion (line 265) | func TestProcessFlagAliasesInvalidPorcelaineVersion(t *testing.T) {
function TestFlagsArePrecentInDocumentation (line 281) | func TestFlagsArePrecentInDocumentation(t *testing.T) {
FILE: internal/meta/meta.go
function init (line 11) | func init() {
FILE: internal/util/rand_name.go
function RandName (line 8) | func RandName() string {
FILE: internal/util/rand_sha256.go
function GenerateRandomSHA256 (line 10) | func GenerateRandomSHA256() string {
function GenerateRandomPrefixedSHA256 (line 15) | func GenerateRandomPrefixedSHA256() string {
FILE: internal/util/util.go
function SliceEqual (line 4) | func SliceEqual(s1, s2 []string) bool {
function SliceSubtract (line 19) | func SliceSubtract(a1, a2 []string) []string {
function StringMapSubtract (line 41) | func StringMapSubtract(m1, m2 map[string]string) map[string]string {
function StructMapSubtract (line 58) | func StructMapSubtract(m1, m2 map[string]struct{}) map[string]struct{} {
FILE: internal/util/util_test.go
function TestSliceEqual_True (line 10) | func TestSliceEqual_True(t *testing.T) {
function TestSliceEqual_DifferentLengths (line 19) | func TestSliceEqual_DifferentLengths(t *testing.T) {
function TestSliceEqual_DifferentContents (line 28) | func TestSliceEqual_DifferentContents(t *testing.T) {
function TestSliceSubtract (line 37) | func TestSliceSubtract(t *testing.T) {
function TestStringMapSubtract (line 47) | func TestStringMapSubtract(t *testing.T) {
function TestStructMapSubtract (line 57) | func TestStructMapSubtract(t *testing.T) {
function TestGenerateRandomSHA256 (line 69) | func TestGenerateRandomSHA256(t *testing.T) {
function TestGenerateRandomPrefixedSHA256 (line 75) | func TestGenerateRandomPrefixedSHA256(t *testing.T) {
FILE: main.go
function init (line 8) | func init() {
function main (line 12) | func main() {
FILE: pkg/api/api.go
constant tokenMissingMsg (line 10) | tokenMissingMsg = "api token is empty or has not been set. exiting"
type API (line 13) | type API struct
method RequireToken (line 27) | func (api *API) RequireToken(fn http.HandlerFunc) http.HandlerFunc {
method RegisterFunc (line 41) | func (api *API) RegisterFunc(path string, fn http.HandlerFunc) {
method RegisterHandler (line 47) | func (api *API) RegisterHandler(path string, handler http.Handler) {
method Start (line 53) | func (api *API) Start(block bool) error {
function New (line 19) | func New(token string) *API {
function runHTTPServer (line 74) | func runHTTPServer() {
FILE: pkg/api/api_test.go
constant token (line 14) | token = "123123123"
function TestAPI (line 17) | func TestAPI(t *testing.T) {
function testHandler (line 63) | func testHandler(w http.ResponseWriter, req *http.Request) {
FILE: pkg/api/metrics/metrics.go
type Handler (line 11) | type Handler struct
function New (line 18) | func New() *Handler {
FILE: pkg/api/metrics/metrics_test.go
constant token (line 20) | token = "123123123"
constant getURL (line 21) | getURL = "http://localhost:8080/v1/metrics"
function TestMetrics (line 24) | func TestMetrics(t *testing.T) {
function getWithToken (line 29) | func getWithToken(handler http.Handler) map[string]string {
FILE: pkg/api/update/update.go
function New (line 17) | func New(updateFn func(images []string), updateLock chan bool) *Handler {
type Handler (line 32) | type Handler struct
method Handle (line 38) | func (handle *Handler) Handle(w http.ResponseWriter, r *http.Request) {
FILE: pkg/container/cgroup_id.go
function GetRunningContainerID (line 14) | func GetRunningContainerID() (cid types.ContainerID, err error) {
function getRunningContainerIDFromString (line 23) | func getRunningContainerIDFromString(s string) types.ContainerID {
FILE: pkg/container/client.go
constant defaultStopSignal (line 23) | defaultStopSignal = "SIGTERM"
type Client (line 27) | type Client interface
function NewClient (line 45) | func NewClient(opts ClientOptions) Client {
type ClientOptions (line 59) | type ClientOptions struct
type WarningStrategy (line 68) | type WarningStrategy
constant WarnAlways (line 72) | WarnAlways WarningStrategy = "always"
constant WarnNever (line 74) | WarnNever WarningStrategy = "never"
constant WarnAuto (line 76) | WarnAuto WarningStrategy = "auto"
type dockerClient (line 79) | type dockerClient struct
method WarnOnHeadPullFailed (line 84) | func (client dockerClient) WarnOnHeadPullFailed(container t.Container)...
method ListContainers (line 95) | func (client dockerClient) ListContainers(fn t.Filter) ([]t.Container,...
method createListFilter (line 135) | func (client dockerClient) createListFilter() filters.Args {
method GetContainer (line 151) | func (client dockerClient) GetContainer(containerID t.ContainerID) (t....
method StopContainer (line 184) | func (client dockerClient) StopContainer(c t.Container, timeout time.D...
method GetNetworkConfig (line 226) | func (client dockerClient) GetNetworkConfig(c t.Container) *network.Ne...
method StartContainer (line 248) | func (client dockerClient) StartContainer(c t.Container) (t.ContainerI...
method doStartContainer (line 302) | func (client dockerClient) doStartContainer(bg context.Context, c t.Co...
method RenameContainer (line 313) | func (client dockerClient) RenameContainer(c t.Container, newName stri...
method IsContainerStale (line 319) | func (client dockerClient) IsContainerStale(container t.Container, par...
method HasNewImage (line 331) | func (client dockerClient) HasNewImage(ctx context.Context, container ...
method PullImage (line 352) | func (client dockerClient) PullImage(ctx context.Context, container t....
method RemoveImageByID (line 408) | func (client dockerClient) RemoveImageByID(id t.ImageID) error {
method ExecuteCommand (line 442) | func (client dockerClient) ExecuteCommand(containerID t.ContainerID, c...
method waitForExecOrTimeout (line 495) | func (client dockerClient) waitForExecOrTimeout(bg context.Context, ID...
method waitForStopOrTimeout (line 541) | func (client dockerClient) waitForStopOrTimeout(c t.Container, waitTim...
FILE: pkg/container/client_test.go
function captureLogrus (line 341) | func captureLogrus(level logrus.Level) (func(), *gbytes.Buffer) {
function withContainerImageName (line 359) | func withContainerImageName(matcher gt.GomegaMatcher) gt.GomegaMatcher {
function containerImageName (line 363) | func containerImageName(container t.Container) string {
function havingRestartingState (line 367) | func havingRestartingState(expected bool) gt.GomegaMatcher {
function havingRunningState (line 373) | func havingRunningState(expected bool) gt.GomegaMatcher {
FILE: pkg/container/container.go
function NewContainer (line 21) | func NewContainer(containerInfo *types.ContainerJSON, imageInfo *types.I...
type Container (line 29) | type Container struct
method IsLinkedToRestarting (line 38) | func (c *Container) IsLinkedToRestarting() bool {
method IsStale (line 43) | func (c *Container) IsStale() bool {
method SetLinkedToRestarting (line 48) | func (c *Container) SetLinkedToRestarting(value bool) {
method SetStale (line 53) | func (c *Container) SetStale(value bool) {
method ContainerInfo (line 58) | func (c Container) ContainerInfo() *types.ContainerJSON {
method ID (line 63) | func (c Container) ID() wt.ContainerID {
method IsRunning (line 70) | func (c Container) IsRunning() bool {
method IsRestarting (line 77) | func (c Container) IsRestarting() bool {
method Name (line 82) | func (c Container) Name() string {
method ImageID (line 88) | func (c Container) ImageID() wt.ImageID {
method SafeImageID (line 94) | func (c Container) SafeImageID() wt.ImageID {
method ImageName (line 104) | func (c Container) ImageName() string {
method Enabled (line 120) | func (c Container) Enabled() (bool, bool) {
method IsMonitorOnly (line 136) | func (c Container) IsMonitorOnly(params wt.UpdateParams) bool {
method IsNoPull (line 142) | func (c Container) IsNoPull(params wt.UpdateParams) bool {
method getContainerOrGlobalBool (line 146) | func (c Container) getContainerOrGlobalBool(globalVal bool, label stri...
method Scope (line 163) | func (c Container) Scope() (string, bool) {
method Links (line 174) | func (c Container) Links() []string {
method ToRestart (line 210) | func (c Container) ToRestart() bool {
method IsWatchtower (line 218) | func (c Container) IsWatchtower() bool {
method PreUpdateTimeout (line 227) | func (c Container) PreUpdateTimeout() int {
method PostUpdateTimeout (line 246) | func (c Container) PostUpdateTimeout() int {
method StopSignal (line 263) | func (c Container) StopSignal() string {
method GetCreateConfig (line 283) | func (c Container) GetCreateConfig() *dockercontainer.Config {
method GetCreateHostConfig (line 352) | func (c Container) GetCreateHostConfig() *dockercontainer.HostConfig {
method HasImageInfo (line 366) | func (c Container) HasImageInfo() bool {
method ImageInfo (line 371) | func (c Container) ImageInfo() *types.ImageInspect {
method VerifyConfiguration (line 377) | func (c Container) VerifyConfiguration() error {
FILE: pkg/container/container_mock_test.go
type MockContainerUpdate (line 9) | type MockContainerUpdate
function MockContainer (line 11) | func MockContainer(updates ...MockContainerUpdate) *Container {
function WithPortBindings (line 34) | func WithPortBindings(portBindingSources ...string) MockContainerUpdate {
function WithImageName (line 44) | func WithImageName(name string) MockContainerUpdate {
function WithLinks (line 51) | func WithLinks(links []string) MockContainerUpdate {
function WithLabels (line 57) | func WithLabels(labels map[string]string) MockContainerUpdate {
function WithContainerState (line 63) | func WithContainerState(state types.ContainerState) MockContainerUpdate {
function WithHealthcheck (line 69) | func WithHealthcheck(healthConfig dockerContainer.HealthConfig) MockCont...
function WithImageHealthcheck (line 75) | func WithImageHealthcheck(healthConfig dockerContainer.HealthConfig) Moc...
FILE: pkg/container/container_suite_test.go
function TestContainer (line 10) | func TestContainer(t *testing.T) {
FILE: pkg/container/metadata.go
constant watchtowerLabel (line 6) | watchtowerLabel = "com.centurylinklabs.watchtower"
constant signalLabel (line 7) | signalLabel = "com.centurylinklabs.watchtower.stop-signal"
constant enableLabel (line 8) | enableLabel = "com.centurylinklabs.watchtower.enable"
constant monitorOnlyLabel (line 9) | monitorOnlyLabel = "com.centurylinklabs.watchtower.monitor-only"
constant noPullLabel (line 10) | noPullLabel = "com.centurylinklabs.watchtower.no-pull"
constant dependsOnLabel (line 11) | dependsOnLabel = "com.centurylinklabs.watchtower.depends-on"
constant zodiacLabel (line 12) | zodiacLabel = "com.centurylinklabs.zodiac.original-image"
constant scope (line 13) | scope = "com.centurylinklabs.watchtower.scope"
constant preCheckLabel (line 14) | preCheckLabel = "com.centurylinklabs.watchtower.lifecycle.pre-c...
constant postCheckLabel (line 15) | postCheckLabel = "com.centurylinklabs.watchtower.lifecycle.post-...
constant preUpdateLabel (line 16) | preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-u...
constant postUpdateLabel (line 17) | postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-...
constant preUpdateTimeoutLabel (line 18) | preUpdateTimeoutLabel = "com.centurylinklabs.watchtower.lifecycle.pre-u...
constant postUpdateTimeoutLabel (line 19) | postUpdateTimeoutLabel = "com.centurylinklabs.watchtower.lifecycle.post-...
method GetLifecyclePreCheckCommand (line 23) | func (c Container) GetLifecyclePreCheckCommand() string {
method GetLifecyclePostCheckCommand (line 28) | func (c Container) GetLifecyclePostCheckCommand() string {
method GetLifecyclePreUpdateCommand (line 33) | func (c Container) GetLifecyclePreUpdateCommand() string {
method GetLifecyclePostUpdateCommand (line 38) | func (c Container) GetLifecyclePostUpdateCommand() string {
function ContainsWatchtowerLabel (line 44) | func ContainsWatchtowerLabel(labels map[string]string) bool {
method getLabelValueOrEmpty (line 49) | func (c Container) getLabelValueOrEmpty(label string) string {
method getLabelValue (line 56) | func (c Container) getLabelValue(label string) (string, bool) {
method getBoolLabelValue (line 61) | func (c Container) getBoolLabelValue(label string) (bool, error) {
FILE: pkg/container/mocks/ApiServer.go
function getMockJSONFile (line 21) | func getMockJSONFile(relPath string) ([]byte, error) {
function RespondWithJSONFile (line 31) | func RespondWithJSONFile(relPath string, statusCode int, optionalHeader ...
function respondWithJSONFile (line 37) | func respondWithJSONFile(relPath string, statusCode int, optionalHeader ...
function GetContainerHandlers (line 46) | func GetContainerHandlers(containerRefs ...*ContainerRef) []http.Handler...
function createFilterArgs (line 65) | func createFilterArgs(statuses []string) filters.Args {
constant NetSupplierNotFoundID (line 140) | NetSupplierNotFoundID = "badc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1...
constant NetSupplierContainerName (line 141) | NetSupplierContainerName = "/wt-contnet-producer-1"
function getContainerFileHandler (line 143) | func getContainerFileHandler(cr *ContainerRef) http.HandlerFunc {
function getContainerHandler (line 160) | func getContainerHandler(containerId string, responseHandler http.Handle...
function GetContainerHandler (line 168) | func GetContainerHandler(containerID string, containerInfo *types.Contai...
function GetImageHandler (line 177) | func GetImageHandler(imageInfo *types.ImageInspect) http.HandlerFunc {
function ListContainersHandler (line 182) | func ListContainersHandler(statuses ...string) http.HandlerFunc {
function respondWithFilteredContainers (line 195) | func respondWithFilteredContainers(filters filters.Args) http.HandlerFunc {
function getImageHandler (line 212) | func getImageHandler(imageId t.ImageID, responseHandler http.HandlerFunc...
function KillContainerHandler (line 220) | func KillContainerHandler(containerID string, found FoundStatus) http.Ha...
function RemoveContainerHandler (line 232) | func RemoveContainerHandler(containerID string, found FoundStatus) http....
function containerNotFoundResponse (line 243) | func containerNotFoundResponse(containerID string) http.HandlerFunc {
type FoundStatus (line 249) | type FoundStatus
constant Found (line 252) | Found FoundStatus = true
constant Missing (line 253) | Missing FoundStatus = false
function RemoveImageHandler (line 257) | func RemoveImageHandler(imagesWithParents map[string][]string) http.Hand...
FILE: pkg/container/mocks/FilterableContainer.go
type FilterableContainer (line 6) | type FilterableContainer struct
method Enabled (line 11) | func (_m *FilterableContainer) Enabled() (bool, bool) {
method IsWatchtower (line 32) | func (_m *FilterableContainer) IsWatchtower() bool {
method Name (line 46) | func (_m *FilterableContainer) Name() string {
method Scope (line 60) | func (_m *FilterableContainer) Scope() (string, bool) {
method ImageName (line 83) | func (_m *FilterableContainer) ImageName() string {
FILE: pkg/container/mocks/container_ref.go
type imageRef (line 10) | type imageRef struct
method getFileName (line 15) | func (ir *imageRef) getFileName() string {
type ContainerRef (line 19) | type ContainerRef struct
method getContainerFile (line 28) | func (cr *ContainerRef) getContainerFile() (containerFile string, err ...
method ContainerID (line 40) | func (cr *ContainerRef) ContainerID() t.ContainerID {
FILE: pkg/container/util_test.go
function shortID (line 47) | func shortID(id string) string {
FILE: pkg/filters/filters.go
function WatchtowerContainersFilter (line 11) | func WatchtowerContainersFilter(c t.FilterableContainer) bool { return c...
function NoFilter (line 14) | func NoFilter(t.FilterableContainer) bool { return true }
function FilterByNames (line 17) | func FilterByNames(names []string, baseFilter t.Filter) t.Filter {
function FilterByDisableNames (line 45) | func FilterByDisableNames(disableNames []string, baseFilter t.Filter) t....
function FilterByEnableLabel (line 61) | func FilterByEnableLabel(baseFilter t.Filter) t.Filter {
function FilterByDisabledLabel (line 75) | func FilterByDisabledLabel(baseFilter t.Filter) t.Filter {
function FilterByScope (line 88) | func FilterByScope(scope string, baseFilter t.Filter) t.Filter {
function FilterByImage (line 105) | func FilterByImage(images []string, baseFilter t.Filter) t.Filter {
function BuildFilter (line 123) | func BuildFilter(names []string, disableNames []string, enableLabel bool...
FILE: pkg/filters/filters_test.go
function TestWatchtowerContainersFilter (line 10) | func TestWatchtowerContainersFilter(t *testing.T) {
function TestNoFilter (line 20) | func TestNoFilter(t *testing.T) {
function TestFilterByNames (line 28) | func TestFilterByNames(t *testing.T) {
function TestFilterByNamesRegex (line 50) | func TestFilterByNamesRegex(t *testing.T) {
function TestFilterByEnableLabel (line 72) | func TestFilterByEnableLabel(t *testing.T) {
function TestFilterByScope (line 92) | func TestFilterByScope(t *testing.T) {
function TestFilterByNoneScope (line 114) | func TestFilterByNoneScope(t *testing.T) {
function TestBuildFilterNoneScope (line 141) | func TestBuildFilterNoneScope(t *testing.T) {
function TestFilterByDisabledLabel (line 161) | func TestFilterByDisabledLabel(t *testing.T) {
function TestFilterByImage (line 181) | func TestFilterByImage(t *testing.T) {
function TestBuildFilter (line 218) | func TestBuildFilter(t *testing.T) {
function TestBuildFilterEnableLabel (line 256) | func TestBuildFilterEnableLabel(t *testing.T) {
function TestBuildFilterDisableContainer (line 286) | func TestBuildFilterDisableContainer(t *testing.T) {
FILE: pkg/lifecycle/lifecycle.go
function ExecutePreChecks (line 10) | func ExecutePreChecks(client container.Client, params types.UpdateParams) {
function ExecutePostChecks (line 21) | func ExecutePostChecks(client container.Client, params types.UpdateParam...
function ExecutePreCheckCommand (line 32) | func ExecutePreCheckCommand(client container.Client, container types.Con...
function ExecutePostCheckCommand (line 48) | func ExecutePostCheckCommand(client container.Client, container types.Co...
function ExecutePreUpdateCommand (line 64) | func ExecutePreUpdateCommand(client container.Client, container types.Co...
function ExecutePostUpdateCommand (line 84) | func ExecutePostUpdateCommand(client container.Client, newContainerID ty...
FILE: pkg/metrics/metrics.go
type Metric (line 12) | type Metric struct
type Metrics (line 19) | type Metrics struct
method QueueIsEmpty (line 39) | func (metrics *Metrics) QueueIsEmpty() bool {
method Register (line 44) | func (metrics *Metrics) Register(metric *Metric) {
method HandleUpdate (line 90) | func (metrics *Metrics) HandleUpdate(channel <-chan *Metric) {
function NewMetric (line 29) | func NewMetric(report types.Report) *Metric {
function Default (line 49) | func Default() *Metrics {
function RegisterScan (line 84) | func RegisterScan(metric *Metric) {
FILE: pkg/notifications/email.go
constant emailType (line 14) | emailType = "email"
type emailTypeNotifier (line 17) | type emailTypeNotifier struct
method GetURL (line 53) | func (e *emailTypeNotifier) GetURL(c *cobra.Command) (string, error) {
method GetDelay (line 80) | func (e *emailTypeNotifier) GetDelay() time.Duration {
function newEmailNotifier (line 26) | func newEmailNotifier(c *cobra.Command) t.ConvertibleNotifier {
FILE: pkg/notifications/gotify.go
constant gotifyType (line 15) | gotifyType = "gotify"
type gotifyTypeNotifier (line 18) | type gotifyTypeNotifier struct
method GetURL (line 63) | func (n *gotifyTypeNotifier) GetURL(c *cobra.Command) (string, error) {
function newGotifyNotifier (line 24) | func newGotifyNotifier(c *cobra.Command) t.ConvertibleNotifier {
function getGotifyToken (line 41) | func getGotifyToken(flags *pflag.FlagSet) string {
function getGotifyURL (line 49) | func getGotifyURL(flags *pflag.FlagSet) string {
FILE: pkg/notifications/json.go
method MarshalJSON (line 12) | func (d Data) MarshalJSON() ([]byte, error) {
function marshalReports (line 43) | func marshalReports(reports []t.ContainerReport) []jsonMap {
FILE: pkg/notifications/model.go
type StaticData (line 9) | type StaticData struct
type Data (line 15) | type Data struct
FILE: pkg/notifications/msteams.go
constant msTeamsType (line 13) | msTeamsType = "msteams"
type msTeamsTypeNotifier (line 16) | type msTeamsTypeNotifier struct
method GetURL (line 39) | func (n *msTeamsTypeNotifier) GetURL(c *cobra.Command) (string, error) {
function newMsTeamsNotifier (line 21) | func newMsTeamsNotifier(cmd *cobra.Command) t.ConvertibleNotifier {
FILE: pkg/notifications/notifications_suite_test.go
function TestNotifications (line 11) | func TestNotifications(t *testing.T) {
FILE: pkg/notifications/notifier.go
function NewNotifier (line 14) | func NewNotifier(c *cobra.Command) ty.Notifier {
function AppendLegacyUrls (line 35) | func AppendLegacyUrls(urls []string, cmd *cobra.Command) ([]string, time...
function GetDelay (line 85) | func GetDelay(c *cobra.Command, legacyDelay time.Duration) time.Duration {
function GetTitle (line 98) | func GetTitle(hostname string, tag string) string {
function GetTemplateData (line 119) | func GetTemplateData(c *cobra.Command) StaticData {
constant ColorHex (line 144) | ColorHex = "#406170"
constant ColorInt (line 147) | ColorInt = 0x406170
FILE: pkg/notifications/notifier_test.go
function buildExpectedURL (line 355) | func buildExpectedURL(username string, password string, host string, por...
function testURL (line 365) | func testURL(args []string, expectedURL string, expectedDelay time.Durat...
FILE: pkg/notifications/preview/data/data.go
type previewData (line 13) | type previewData struct
method AddFromState (line 43) | func (pb *previewData) AddFromState(state State) {
method addContainer (line 66) | func (pb *previewData) addContainer(c containerStatus) {
method AddLogEntry (line 90) | func (pd *previewData) AddLogEntry(level LogLevel) {
method Report (line 111) | func (pb *previewData) Report() types.Report {
method generateID (line 115) | func (pb *previewData) generateID() string {
method generateTime (line 121) | func (pb *previewData) generateTime() time.Time {
method randomEntry (line 126) | func (pb *previewData) randomEntry(arr []string) string {
method generateName (line 130) | func (pb *previewData) generateName() string {
method generateImageName (line 140) | func (pb *previewData) generateImageName(name string) string {
type staticData (line 22) | type staticData struct
function New (line 28) | func New() *previewData {
FILE: pkg/notifications/preview/data/logs.go
type logEntry (line 7) | type logEntry struct
type LogLevel (line 15) | type LogLevel
method String (line 54) | func (level LogLevel) String() string {
constant TraceLevel (line 18) | TraceLevel LogLevel = "trace"
constant DebugLevel (line 19) | DebugLevel LogLevel = "debug"
constant InfoLevel (line 20) | InfoLevel LogLevel = "info"
constant WarnLevel (line 21) | WarnLevel LogLevel = "warning"
constant ErrorLevel (line 22) | ErrorLevel LogLevel = "error"
constant FatalLevel (line 23) | FatalLevel LogLevel = "fatal"
constant PanicLevel (line 24) | PanicLevel LogLevel = "panic"
function LevelsFromString (line 28) | func LevelsFromString(str string) []LogLevel {
FILE: pkg/notifications/preview/data/report.go
type State (line 10) | type State
constant ScannedState (line 13) | ScannedState State = "scanned"
constant UpdatedState (line 14) | UpdatedState State = "updated"
constant FailedState (line 15) | FailedState State = "failed"
constant SkippedState (line 16) | SkippedState State = "skipped"
constant StaleState (line 17) | StaleState State = "stale"
constant FreshState (line 18) | FreshState State = "fresh"
function StatesFromString (line 22) | func StatesFromString(str string) []State {
type report (line 45) | type report struct
method Scanned (line 54) | func (r *report) Scanned() []types.ContainerReport {
method Updated (line 57) | func (r *report) Updated() []types.ContainerReport {
method Failed (line 60) | func (r *report) Failed() []types.ContainerReport {
method Skipped (line 63) | func (r *report) Skipped() []types.ContainerReport {
method Stale (line 66) | func (r *report) Stale() []types.ContainerReport {
method Fresh (line 69) | func (r *report) Fresh() []types.ContainerReport {
method All (line 73) | func (r *report) All() []types.ContainerReport {
type sortableContainers (line 101) | type sortableContainers
method Len (line 104) | func (s sortableContainers) Len() int { return len(s) }
method Less (line 107) | func (s sortableContainers) Less(i, j int) bool { return s[i].ID() < s...
method Swap (line 110) | func (s sortableContainers) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
FILE: pkg/notifications/preview/data/status.go
type containerStatus (line 5) | type containerStatus struct
method ID (line 15) | func (u *containerStatus) ID() wt.ContainerID {
method Name (line 19) | func (u *containerStatus) Name() string {
method CurrentImageID (line 23) | func (u *containerStatus) CurrentImageID() wt.ImageID {
method LatestImageID (line 27) | func (u *containerStatus) LatestImageID() wt.ImageID {
method ImageName (line 31) | func (u *containerStatus) ImageName() string {
method Error (line 35) | func (u *containerStatus) Error() string {
method State (line 42) | func (u *containerStatus) State() string {
FILE: pkg/notifications/preview/tplprev.go
function Render (line 12) | func Render(input string, states []data.State, loglevels []data.LogLevel...
FILE: pkg/notifications/shoutrrr.go
constant shoutrrrType (line 22) | shoutrrrType = "shoutrrr"
type router (line 25) | type router interface
type shoutrrrTypeNotifier (line 30) | type shoutrrrTypeNotifier struct
method GetNames (line 55) | func (n *shoutrrrTypeNotifier) GetNames() []string {
method GetURLs (line 64) | func (n *shoutrrrTypeNotifier) GetURLs() []string {
method AddLogHook (line 69) | func (n *shoutrrrTypeNotifier) AddLogHook() {
method buildMessage (line 136) | func (n *shoutrrrTypeNotifier) buildMessage(data Data) (string, error) {
method sendEntries (line 149) | func (n *shoutrrrTypeNotifier) sendEntries(entries []*log.Entry, repor...
method StartNotification (line 167) | func (n *shoutrrrTypeNotifier) StartNotification() {
method SendNotification (line 174) | func (n *shoutrrrTypeNotifier) SendNotification(report t.Report) {
method Close (line 180) | func (n *shoutrrrTypeNotifier) Close() {
method Levels (line 190) | func (n *shoutrrrTypeNotifier) Levels() []log.Level {
method Fire (line 195) | func (n *shoutrrrTypeNotifier) Fire(entry *log.Entry) error {
function GetScheme (line 46) | func GetScheme(url string) string {
function createNotifier (line 80) | func createNotifier(urls []string, level log.Level, tplString string, le...
function sendNotifications (line 116) | func sendNotifications(n *shoutrrrTypeNotifier) {
function getShoutrrrTemplate (line 209) | func getShoutrrrTemplate(tplString string, legacy bool) (tpl *template.T...
FILE: pkg/notifications/shoutrrr_test.go
function mockDataFromStates (line 50) | func mockDataFromStates(states ...s.State) Data {
type blockingRouter (line 321) | type blockingRouter struct
method Send (line 326) | func (b blockingRouter) Send(_ string, _ *types.Params) []error {
function sendNotificationsWithBlockingRouter (line 332) | func sendNotificationsWithBlockingRouter(legacy bool) (*shoutrrrTypeNoti...
function createNotifierWithTemplate (line 366) | func createNotifierWithTemplate(tplString string, legacy bool) (*shoutrr...
function getTemplatedResult (line 375) | func getTemplatedResult(tplString string, legacy bool, data Data) (msg s...
FILE: pkg/notifications/slack.go
constant slackType (line 14) | slackType = "slack"
type slackTypeNotifier (line 17) | type slackTypeNotifier struct
method GetURL (line 44) | func (s *slackTypeNotifier) GetURL(c *cobra.Command) (string, error) {
function newSlackNotifier (line 25) | func newSlackNotifier(c *cobra.Command) t.ConvertibleNotifier {
FILE: pkg/notifications/templates/funcs.go
function toJSON (line 20) | func toJSON(v interface{}) string {
FILE: pkg/registry/auth/auth.go
constant ChallengeHeader (line 19) | ChallengeHeader = "WWW-Authenticate"
function GetToken (line 22) | func GetToken(container types.Container, registryAuth string) (string, e...
function GetChallengeRequest (line 65) | func GetChallengeRequest(URL url.URL) (*http.Request, error) {
function GetBearerHeader (line 76) | func GetBearerHeader(challenge string, imageRef ref.Named, registryAuth ...
function GetAuthURL (line 115) | func GetAuthURL(challenge string, imageRef ref.Named) (*url.URL, error) {
function GetChallengeURL (line 153) | func GetChallengeURL(imageRef ref.Named) url.URL {
FILE: pkg/registry/auth/auth_test.go
function TestAuth (line 20) | func TestAuth(t *testing.T) {
function SkipIfCredentialsEmpty (line 24) | func SkipIfCredentialsEmpty(credentials *wtTypes.RegistryCredentials, fn...
function getScopeFromImageAuthURL (line 154) | func getScopeFromImageAuthURL(imageName string) string {
FILE: pkg/registry/digest/digest.go
constant ContentDigestHeader (line 22) | ContentDigestHeader = "Docker-Content-Digest"
function CompareDigest (line 25) | func CompareDigest(container types.Container, registryAuth string) (bool...
function TransformAuth (line 64) | func TransformAuth(registryAuth string) string {
function GetDigest (line 78) | func GetDigest(url string, token string) (string, error) {
FILE: pkg/registry/digest/digest_test.go
function TestDigest (line 17) | func TestDigest(t *testing.T) {
function SkipIfCredentialsEmpty (line 34) | func SkipIfCredentialsEmpty(credentials *wtTypes.RegistryCredentials, fn...
FILE: pkg/registry/helpers/helpers.go
constant DefaultRegistryDomain (line 9) | DefaultRegistryDomain = "docker.io"
constant DefaultRegistryHost (line 10) | DefaultRegistryHost = "index.docker.io"
constant LegacyDefaultRegistryDomain (line 11) | LegacyDefaultRegistryDomain = "index.docker.io"
function GetRegistryAddress (line 16) | func GetRegistryAddress(imageRef string) (string, error) {
FILE: pkg/registry/helpers/helpers_test.go
function TestHelpers (line 10) | func TestHelpers(t *testing.T) {
FILE: pkg/registry/manifest/manifest.go
function BuildManifestURL (line 15) | func BuildManifestURL(container types.Container) (string, error) {
FILE: pkg/registry/manifest/manifest_test.go
function TestManifest (line 14) | func TestManifest(t *testing.T) {
function buildMockContainerManifestURL (line 62) | func buildMockContainerManifestURL(imageRef string) (string, error) {
FILE: pkg/registry/registry.go
function GetPullOptions (line 12) | func GetPullOptions(imageName string) (types.ImagePullOptions, error) {
function DefaultAuthHandler (line 35) | func DefaultAuthHandler() (string, error) {
function WarnOnAPIConsumption (line 44) | func WarnOnAPIConsumption(container watchtowerTypes.Container) bool {
FILE: pkg/registry/registry_suite_test.go
function TestRegistry (line 11) | func TestRegistry(t *testing.T) {
FILE: pkg/registry/registry_test.go
function testContainerWithImage (line 40) | func testContainerWithImage(imageName string) bool {
FILE: pkg/registry/trust.go
function EncodedAuth (line 20) | func EncodedAuth(ref string) (string, error) {
function EncodedEnvAuth (line 31) | func EncodedEnvAuth() (string, error) {
function EncodedConfigAuth (line 53) | func EncodedConfigAuth(imageRef string) (string, error) {
function CredentialsStore (line 84) | func CredentialsStore(configFile configfile.ConfigFile) credentials.Store {
function EncodeAuth (line 92) | func EncodeAuth(authConfig types.AuthConfig) (string, error) {
FILE: pkg/session/container_status.go
type State (line 6) | type State
constant UnknownState (line 11) | UnknownState State = iota
constant SkippedState (line 12) | SkippedState
constant ScannedState (line 13) | ScannedState
constant UpdatedState (line 14) | UpdatedState
constant FailedState (line 15) | FailedState
constant FreshState (line 16) | FreshState
constant StaleState (line 17) | StaleState
type ContainerStatus (line 21) | type ContainerStatus struct
method ID (line 32) | func (u *ContainerStatus) ID() wt.ContainerID {
method Name (line 37) | func (u *ContainerStatus) Name() string {
method CurrentImageID (line 42) | func (u *ContainerStatus) CurrentImageID() wt.ImageID {
method LatestImageID (line 47) | func (u *ContainerStatus) LatestImageID() wt.ImageID {
method ImageName (line 52) | func (u *ContainerStatus) ImageName() string {
method Error (line 57) | func (u *ContainerStatus) Error() string {
method State (line 65) | func (u *ContainerStatus) State() string {
FILE: pkg/session/progress.go
type Progress (line 8) | type Progress
method AddSkipped (line 23) | func (m Progress) AddSkipped(cont types.Container, err error) {
method AddScanned (line 30) | func (m Progress) AddScanned(cont types.Container, newImage types.Imag...
method UpdateFailed (line 35) | func (m Progress) UpdateFailed(failures map[types.ContainerID]error) {
method Add (line 44) | func (m Progress) Add(update *ContainerStatus) {
method MarkForUpdate (line 49) | func (m Progress) MarkForUpdate(containerID types.ContainerID) {
method Report (line 54) | func (m Progress) Report() types.Report {
function UpdateFromContainer (line 11) | func UpdateFromContainer(cont types.Container, newImage types.ImageID, s...
FILE: pkg/session/report.go
type report (line 9) | type report struct
method Scanned (line 18) | func (r *report) Scanned() []types.ContainerReport {
method Updated (line 21) | func (r *report) Updated() []types.ContainerReport {
method Failed (line 24) | func (r *report) Failed() []types.ContainerReport {
method Skipped (line 27) | func (r *report) Skipped() []types.ContainerReport {
method Stale (line 30) | func (r *report) Stale() []types.ContainerReport {
method Fresh (line 33) | func (r *report) Fresh() []types.ContainerReport {
method All (line 36) | func (r *report) All() []types.ContainerReport {
function NewReport (line 65) | func NewReport(progress Progress) types.Report {
type sortableContainers (line 109) | type sortableContainers
method Len (line 112) | func (s sortableContainers) Len() int { return len(s) }
method Less (line 115) | func (s sortableContainers) Less(i, j int) bool { return s[i].ID() < s...
method Swap (line 118) | func (s sortableContainers) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
FILE: pkg/sorter/sort.go
type ByCreated (line 12) | type ByCreated
method Len (line 14) | func (c ByCreated) Len() int { return len(c) }
method Swap (line 15) | func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
method Less (line 19) | func (c ByCreated) Less(i, j int) bool {
function SortByDependencies (line 38) | func SortByDependencies(containers []types.Container) ([]types.Container...
type dependencySorter (line 43) | type dependencySorter struct
method Sort (line 49) | func (ds *dependencySorter) Sort(containers []types.Container) ([]type...
method visit (line 62) | func (ds *dependencySorter) visit(c types.Container) error {
method findUnvisited (line 88) | func (ds *dependencySorter) findUnvisited(name string) *types.Container {
method removeUnvisited (line 98) | func (ds *dependencySorter) removeUnvisited(c types.Container) {
FILE: pkg/types/container.go
type ImageID (line 11) | type ImageID
method ShortID (line 17) | func (id ImageID) ShortID() (short string) {
type ContainerID (line 14) | type ContainerID
method ShortID (line 22) | func (id ContainerID) ShortID() (short string) {
function shortID (line 26) | func shortID(longID string) string {
type Container (line 46) | type Container interface
FILE: pkg/types/convertible_notifier.go
type ConvertibleNotifier (line 10) | type ConvertibleNotifier interface
type DelayNotifier (line 15) | type DelayNotifier interface
FILE: pkg/types/filter.go
type Filter (line 5) | type Filter
FILE: pkg/types/filterable_container.go
type FilterableContainer (line 5) | type FilterableContainer interface
FILE: pkg/types/notifier.go
type Notifier (line 4) | type Notifier interface
FILE: pkg/types/registry_credentials.go
type RegistryCredentials (line 4) | type RegistryCredentials struct
FILE: pkg/types/report.go
type Report (line 4) | type Report interface
type ContainerReport (line 15) | type ContainerReport interface
FILE: pkg/types/token_response.go
type TokenResponse (line 4) | type TokenResponse struct
FILE: pkg/types/update_params.go
type UpdateParams (line 8) | type UpdateParams struct
FILE: tplprev/main.go
function main (line 15) | func main() {
FILE: tplprev/main_wasm.go
function main (line 15) | func main() {
function jsTplPrev (line 25) | func jsTplPrev(this js.Value, args []js.Value) any {
Condensed preview — 165 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (664K chars).
[
{
"path": ".all-contributorsrc",
"chars": 23198,
"preview": "{\n \"files\": [\n \"README.md\"\n ],\n \"imageSize\": 100,\n \"commit\": false,\n \"contributors\": [\n {\n \"login\": \"pik"
},
{
"path": ".codacy.yml",
"chars": 76,
"preview": "---\nengines:\n coverage:\n exclude_paths:\n - \"*.md\"\n - \"**/*.md\""
},
{
"path": ".devbots/lock-issue.yml",
"chars": 224,
"preview": "enabled: true\ncomment: >\n To avoid important communication to get lost in a closed issues no one monitors, I'll go ahea"
},
{
"path": ".editorconfig",
"chars": 182,
"preview": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\n\n[*.css]\nindent_style = space\nindent_size "
},
{
"path": ".github/CODEOWNERS",
"chars": 360,
"preview": "pkg/notifications/smtp.go @piksel\npkg/notifications/email.go @piksel\npkg/notifications/shoutrrr.go @piksel @simsk"
},
{
"path": ".github/ISSUE_TEMPLATE/bug.yml",
"chars": 1886,
"preview": "name: 🐛 Bug report\ndescription: Create a report to help us improve\nlabels: [\"Priority: Medium, Status: Available, Type: "
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 196,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Ask a question\n url: https://github.com/containrrr/watchtower/di"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 1119,
"preview": "name: 💡 Feature request\ndescription: Have a new idea/feature ? Please suggest!\nlabels: [\"Priority: Low, Status: Availabl"
},
{
"path": ".github/dependabot.yml",
"chars": 855,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/pull_request_template.md",
"chars": 457,
"preview": "<!--\n\nThank you for contributing to the watchtower project! 🙏\n\nWe truly appreciate all the contributions we get from the"
},
{
"path": ".github/stale.yml",
"chars": 406,
"preview": "daysUntilStale: 60\ndaysUntilClose: 7\nexemptMilestones: true\nexemptLabels:\n - \"Public Service Announcement\"\n - \"Do not "
},
{
"path": ".github/workflows/codeql-analysis.yml",
"chars": 2586,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".github/workflows/dependabot-approve.yml",
"chars": 249,
"preview": "name: Auto approve dependabot PRs\n\non: pull_request_target\n\njobs:\n auto-approve:\n runs-on: ubuntu-latest\n permiss"
},
{
"path": ".github/workflows/greetings.yml",
"chars": 1028,
"preview": "name: Greetings\n\non:\n # Runs in the context of the target (containrrr/watchtower) repository, and as such has access to"
},
{
"path": ".github/workflows/publish-docs.yml",
"chars": 868,
"preview": "name: Publish Docs\n\non:\n workflow_dispatch: { }\n workflow_run:\n workflows: [ \"Release (Production)\" ]\n branches:"
},
{
"path": ".github/workflows/pull-request.yml",
"chars": 1734,
"preview": "name: Pull Request\n\non:\n workflow_dispatch: {}\n pull_request:\n branches:\n - main\n\njobs:\n lint:\n name: Lint"
},
{
"path": ".github/workflows/release-dev.yaml",
"chars": 1665,
"preview": "name: Push to main\n\non:\n workflow_dispatch: {}\n push:\n branches:\n - main\n\njobs:\n build:\n runs-on: ubuntu-l"
},
{
"path": ".github/workflows/release.yml",
"chars": 6460,
"preview": "name: Release (Production)\n\non:\n workflow_dispatch: {}\n push:\n tags:\n - 'v[0-9]+.[0-9]+.[0-9]+'\n - '**/v["
},
{
"path": ".gitignore",
"chars": 139,
"preview": "watchtower\nwatchtower.exe\nvendor\n.glide\ndist\n.idea\n.DS_Store\n/site\ncoverage.out\n*.coverprofile\n\ndocs/assets/wasm_exec.js"
},
{
"path": "CONTRIBUTING.md",
"chars": 1746,
"preview": "## Prerequisites\nTo contribute code changes to this project you will need the following development kits.\n * [Go](https:"
},
{
"path": "LICENSE.md",
"chars": 11526,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 38310,
"preview": "<div align=\"center\">\n\n ### ⚠️ This project is no longer maintained\n See https://github.com/containrrr/watchtower/discu"
},
{
"path": "SECURITY.md",
"chars": 696,
"preview": "# Security Policy\n\n## Supported Versions\n\nSecurity updates will always only be applied to the latest version of Watchtow"
},
{
"path": "build.sh",
"chars": 247,
"preview": "#!/bin/bash\n\nBINFILE=watchtower\nif [ -n \"$MSYSTEM\" ]; then\n BINFILE=watchtower.exe\nfi\nVERSION=$(git describe --tags)\n"
},
{
"path": "cmd/notify-upgrade.go",
"chars": 2852,
"preview": "// Package cmd contains the watchtower (sub-)commands\npackage cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"sysca"
},
{
"path": "cmd/root.go",
"chars": 10819,
"preview": "package cmd\n\nimport (\n\t\"errors\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"githu"
},
{
"path": "code_of_conduct.md",
"chars": 171,
"preview": "### Containrrr Community Code of Conduct\n\nPlease refer to out [Containrrr Community Code of Conduct](https://github.com/"
},
{
"path": "docker-compose.yml",
"chars": 1054,
"preview": "version: '3.7'\n\nservices:\n watchtower:\n container_name: watchtower\n build:\n context: ./\n dockerfile: do"
},
{
"path": "dockerfiles/Dockerfile",
"chars": 459,
"preview": "FROM --platform=$BUILDPLATFORM alpine:3.19.0 as alpine\n\nRUN apk add --no-cache \\\n ca-certificates \\\n tzdata\n\nFROM "
},
{
"path": "dockerfiles/Dockerfile.dev-self-contained",
"chars": 1150,
"preview": "#\n# Builder\n#\n\nFROM golang:alpine as builder\n\n# use version (for example \"v0.3.3\") or \"main\"\nARG WATCHTOWER_VERSION=main"
},
{
"path": "dockerfiles/Dockerfile.self-contained",
"chars": 944,
"preview": "#\n# Builder\n#\n\nFROM golang:alpine as builder\n\n# use version (for example \"v0.3.3\") or \"main\"\nARG WATCHTOWER_VERSION=main"
},
{
"path": "dockerfiles/container-networking/docker-compose.yml",
"chars": 477,
"preview": "services:\n producer:\n image: qmcgaw/gluetun:v3.35.0\n cap_add:\n - NET_ADMIN\n environment:\n - VPN_SERV"
},
{
"path": "docs/arguments.md",
"chars": 16374,
"preview": "By default, watchtower will monitor all containers running within the Docker daemon to which it is pointed (in most case"
},
{
"path": "docs/container-selection.md",
"chars": 3349,
"preview": "By default, watchtower will watch all containers. However, sometimes only some containers should be updated.\n\nThere are "
},
{
"path": "docs/http-api-mode.md",
"chars": 1864,
"preview": "Watchtower provides an HTTP API mode that enables an HTTP endpoint that can be requested to trigger container updating. "
},
{
"path": "docs/index.md",
"chars": 2752,
"preview": "<p style=\"text-align: center; margin-left: 1.6rem;\">\n <img alt=\"Logotype depicting a lighthouse\" src=\"./images/logo-450"
},
{
"path": "docs/introduction.md",
"chars": 1416,
"preview": "Watchtower is an application that will monitor your running Docker containers and watch for changes to the images that t"
},
{
"path": "docs/lifecycle-hooks.md",
"chars": 3353,
"preview": "## Executing commands before and after updating\n\n!!! note \n These are shell commands executed with `sh`, and therefor"
},
{
"path": "docs/linked-containers.md",
"chars": 1150,
"preview": "Watchtower will detect if there are links between any of the running containers and ensures that things are stopped/star"
},
{
"path": "docs/metrics.md",
"chars": 1963,
"preview": "!!! warning \"Experimental feature\"\n This feature was added in v1.0.4 and is still considered experimental. If you not"
},
{
"path": "docs/notifications.md",
"chars": 17948,
"preview": "# Notifications\n\nWatchtower can send notifications when containers are updated. Notifications are sent via hooks in the "
},
{
"path": "docs/private-registries.md",
"chars": 7772,
"preview": "Watchtower supports private Docker image registries. In many cases, accessing a private registry\nrequires a valid userna"
},
{
"path": "docs/remote-hosts.md",
"chars": 715,
"preview": "By default, watchtower is set-up to monitor the local Docker daemon (the same daemon running the watchtower container it"
},
{
"path": "docs/running-multiple-instances.md",
"chars": 1919,
"preview": "By default, Watchtower will clean up other instances and won't allow multiple instances running on the same Docker host "
},
{
"path": "docs/secure-connections.md",
"chars": 1057,
"preview": "Watchtower is also capable of connecting to Docker endpoints which are protected by SSL/TLS. If you've used _docker-mach"
},
{
"path": "docs/stop-signals.md",
"chars": 674,
"preview": "When watchtower detects that a running container needs to be updated it will stop the container by sending it a SIGTERM "
},
{
"path": "docs/stylesheets/theme.css",
"chars": 3763,
"preview": "[data-md-color-scheme=\"containrrr\"] {\n /* Primary and accent */\n --md-primary-fg-color: #406170;\n --md-primary-fg-col"
},
{
"path": "docs/template-preview.md",
"chars": 7999,
"preview": "<style>\n #tplprev {\n margin: 0;\n display: flex; \n flex-direction: column; \n row-gap: 1rem"
},
{
"path": "docs/updating.md",
"chars": 420,
"preview": "## Updating Watchtower\n\nIf watchtower is monitoring the same Docker daemon under which the watchtower container itself i"
},
{
"path": "docs/usage-overview.md",
"chars": 3412,
"preview": "Watchtower is itself packaged as a Docker container so installation is as simple as pulling the `containrrr/watchtower` "
},
{
"path": "docs-requirements.txt",
"chars": 30,
"preview": "mkdocs\nmkdocs-material\nmd-toc\n"
},
{
"path": "go.mod",
"chars": 3141,
"preview": "module github.com/containrrr/watchtower\n\ngo 1.20\n\nrequire (\n\tgithub.com/containrrr/shoutrrr v0.8.0\n\tgithub.com/distribut"
},
{
"path": "go.sum",
"chars": 22260,
"preview": "github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=\ngithub.c"
},
{
"path": "goreleaser.yml",
"chars": 2254,
"preview": "build:\n main: ./main.go\n binary: watchtower\n goos:\n - linux\n - windows\n goarch:\n - amd64\n - 386\n - ar"
},
{
"path": "grafana/dashboards/dashboard.json",
"chars": 6375,
"preview": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": \"-- Grafana --\",\n \"enable\""
},
{
"path": "grafana/dashboards/dashboard.yml",
"chars": 199,
"preview": "apiVersion: 1\n\nproviders:\n - name: 'Prometheus'\n orgId: 1\n folder: ''\n type: file\n disableDeletion: false\n "
},
{
"path": "grafana/datasources/datasource.yml",
"chars": 139,
"preview": "apiVersion: 1\n\ndatasources:\n - name: Prometheus\n type: prometheus\n access: proxy\n url: http://prometheus:9090\n"
},
{
"path": "internal/actions/actions_suite_test.go",
"chars": 3178,
"preview": "package actions_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/containrrr/watchtower/int"
},
{
"path": "internal/actions/check.go",
"chars": 2578,
"preview": "package actions\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/containrrr/watchtower/pkg/container\"\n\t\"github.com/contain"
},
{
"path": "internal/actions/mocks/client.go",
"chars": 2790,
"preview": "package mocks\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tt \"github.com/containrrr/watchtower/pkg/types\"\n)\n\n// MockClient is a "
},
{
"path": "internal/actions/mocks/container.go",
"chars": 4336,
"preview": "package mocks\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/containrrr/watchtower/pkg/container\"\n\twt \"git"
},
{
"path": "internal/actions/mocks/progress.go",
"chars": 1294,
"preview": "package mocks\n\nimport (\n\t\"errors\"\n\n\t\"github.com/containrrr/watchtower/pkg/session\"\n\twt \"github.com/containrrr/watchtower"
},
{
"path": "internal/actions/update.go",
"chars": 8432,
"preview": "package actions\n\nimport (\n\t\"errors\"\n\n\t\"github.com/containrrr/watchtower/internal/util\"\n\t\"github.com/containrrr/watchtowe"
},
{
"path": "internal/actions/update_test.go",
"chars": 14296,
"preview": "package actions_test\n\nimport (\n\t\"time\"\n\n\t\"github.com/containrrr/watchtower/internal/actions\"\n\t\"github.com/containrrr/wat"
},
{
"path": "internal/flags/flags.go",
"chars": 19432,
"preview": "package flags\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\tlog \"github.com/sirupsen/logrus\"\n"
},
{
"path": "internal/flags/flags_test.go",
"chars": 9611,
"preview": "package flags\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"github.co"
},
{
"path": "internal/meta/meta.go",
"chars": 246,
"preview": "package meta\n\nvar (\n\t// Version is the compile-time set version of Watchtower\n\tVersion = \"v0.0.0-unknown\"\n\n\t// UserAgent"
},
{
"path": "internal/util/rand_name.go",
"chars": 326,
"preview": "package util\n\nimport \"math/rand\"\n\nvar letters = []rune(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\")\n\n// RandN"
},
{
"path": "internal/util/rand_sha256.go",
"chars": 560,
"preview": "package util\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"fmt\"\n)\n\n// GenerateRandomSHA256 generates a random 64 character SHA 256"
},
{
"path": "internal/util/util.go",
"chars": 1144,
"preview": "package util\n\n// SliceEqual compares two slices and checks whether they have equal content\nfunc SliceEqual(s1, s2 []stri"
},
{
"path": "internal/util/util_test.go",
"chars": 2002,
"preview": "package util\n\nimport (\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSliceEqual_True(t *testin"
},
{
"path": "main.go",
"chars": 177,
"preview": "package main\n\nimport (\n\t\"github.com/containrrr/watchtower/cmd\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nfunc init() {\n\tlog.S"
},
{
"path": "mkdocs.yml",
"chars": 1627,
"preview": "site_name: Watchtower\nsite_url: https://containrrr.dev/watchtower/\nrepo_url: https://github.com/containrrr/watchtower/\ne"
},
{
"path": "pkg/api/api.go",
"chars": 1801,
"preview": "package api\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst tokenMissingMsg = \"api token is emp"
},
{
"path": "pkg/api/api_test.go",
"chars": 1417,
"preview": "package api\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi"
},
{
"path": "pkg/api/metrics/metrics.go",
"chars": 527,
"preview": "package metrics\n\nimport (\n\t\"github.com/containrrr/watchtower/pkg/metrics\"\n\t\"net/http\"\n\n\t\"github.com/prometheus/client_go"
},
{
"path": "pkg/api/metrics/metrics_test.go",
"chars": 2180,
"preview": "package metrics_test\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t. \"github.com/onsi/"
},
{
"path": "pkg/api/update/update.go",
"chars": 1348,
"preview": "package update\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tlock chan bool\n"
},
{
"path": "pkg/container/cgroup_id.go",
"chars": 729,
"preview": "package container\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\n\t\"github.com/containrrr/watchtower/pkg/types\"\n)\n\nvar dockerContainer"
},
{
"path": "pkg/container/cgroup_id_test.go",
"chars": 1854,
"preview": "package container\n\nimport (\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"GetRunningConta"
},
{
"path": "pkg/container/client.go",
"chars": 15859,
"preview": "package container\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/docker/docker/api/types\"\n\t\"github.com"
},
{
"path": "pkg/container/client_test.go",
"chars": 13953,
"preview": "package container\n\nimport (\n\t\"github.com/docker/docker/api/types/network\"\n\t\"time\"\n\n\t\"github.com/containrrr/watchtower/in"
},
{
"path": "pkg/container/container.go",
"chars": 12578,
"preview": "// Package container contains code related to dealing with docker containers\npackage container\n\nimport (\n\t\"errors\"\n\t\"fmt"
},
{
"path": "pkg/container/container_mock_test.go",
"chars": 2204,
"preview": "package container\n\nimport (\n\t\"github.com/docker/docker/api/types\"\n\tdockerContainer \"github.com/docker/docker/api/types/c"
},
{
"path": "pkg/container/container_suite_test.go",
"chars": 200,
"preview": "package container_test\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestContain"
},
{
"path": "pkg/container/container_test.go",
"chars": 13696,
"preview": "package container\n\nimport (\n\t\"github.com/containrrr/watchtower/pkg/types\"\n\tdc \"github.com/docker/docker/api/types/contai"
},
{
"path": "pkg/container/errors.go",
"chars": 320,
"preview": "package container\n\nimport \"errors\"\n\nvar errorNoImageInfo = errors.New(\"no available image info\")\nvar errorNoContainerInf"
},
{
"path": "pkg/container/metadata.go",
"chars": 2750,
"preview": "package container\n\nimport \"strconv\"\n\nconst (\n\twatchtowerLabel = \"com.centurylinklabs.watchtower\"\n\tsignalLabel "
},
{
"path": "pkg/container/mocks/ApiServer.go",
"chars": 9155,
"preview": "package mocks\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/onsi/ginkgo\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n"
},
{
"path": "pkg/container/mocks/FilterableContainer.go",
"chars": 1703,
"preview": "package mocks\n\nimport mock \"github.com/stretchr/testify/mock\"\n\n// FilterableContainer is an autogenerated mock type for "
},
{
"path": "pkg/container/mocks/container_ref.go",
"chars": 747,
"preview": "package mocks\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\tt \"github.com/containrrr/watchtower/pkg/types\"\n)\n\ntype imageRef struct {\n\tid t."
},
{
"path": "pkg/container/mocks/data/container_net_consumer-missing_supplier.json",
"chars": 6661,
"preview": "{\n \"Id\": \"1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6\",\n \"Created\": \"2023-07-25T14:55:14.69155887"
},
{
"path": "pkg/container/mocks/data/container_net_consumer.json",
"chars": 6661,
"preview": "{\n \"Id\": \"1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6\",\n \"Created\": \"2023-07-25T14:55:14.69155887"
},
{
"path": "pkg/container/mocks/data/container_net_supplier.json",
"chars": 12604,
"preview": "{\n \"Id\": \"25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2\",\n \"Created\": \"2023-07-25T14:55:14.59566262"
},
{
"path": "pkg/container/mocks/data/container_restarting.json",
"chars": 5947,
"preview": "{\n \"Id\": \"ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b67\",\n \"Created\": \"2019-04-10T19:51:22.24504100"
},
{
"path": "pkg/container/mocks/data/container_running.json",
"chars": 6572,
"preview": "{\n \"Id\": \"b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008\",\n \"Created\": \"2019-04-04T20:28:32.5710901Z"
},
{
"path": "pkg/container/mocks/data/container_stopped.json",
"chars": 5951,
"preview": "{\n \"Id\": \"ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65\",\n \"Created\": \"2019-04-10T19:51:22.24504100"
},
{
"path": "pkg/container/mocks/data/container_watchtower.json",
"chars": 5954,
"preview": "{\n \"Id\": \"3d88e0e3543281c747d88b27e246578b65ae8964ba86c7cd7522cf84e0978134\",\n \"Created\": \"2020-04-10T19:51:22.24504100"
},
{
"path": "pkg/container/mocks/data/containers.json",
"chars": 6013,
"preview": "[\n {\n \"Id\": \"ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65\",\n \"Names\": [\n \"/watchtower-st"
},
{
"path": "pkg/container/mocks/data/image_default.json",
"chars": 2732,
"preview": "{\n \"Id\": \"sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd\",\n \"RepoTags\": [\n \"portainer/port"
},
{
"path": "pkg/container/mocks/data/image_net_consumer.json",
"chars": 3834,
"preview": "{\n \"Id\": \"sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8\",\n \"RepoTags\": [\n \"nginx:latest\"\n"
},
{
"path": "pkg/container/mocks/data/image_net_producer.json",
"chars": 7193,
"preview": "{\n \"Id\": \"sha256:c22b543d33bfdcb9992cbef23961677133cdf09da71d782468ae2517138bad51\",\n \"RepoTags\": [\n \"qmcgaw/gluetun"
},
{
"path": "pkg/container/mocks/data/image_running.json",
"chars": 2850,
"preview": "{\n \"Id\": \"sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa\",\n \"RepoTags\": [\n \"containrrr/wat"
},
{
"path": "pkg/container/util_test.go",
"chars": 1693,
"preview": "package container_test\n\nimport (\n\twt \"github.com/containrrr/watchtower/pkg/types\"\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github"
},
{
"path": "pkg/filters/filters.go",
"chars": 4713,
"preview": "package filters\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\tt \"github.com/containrrr/watchtower/pkg/types\"\n)\n\n// WatchtowerContaine"
},
{
"path": "pkg/filters/filters_test.go",
"chars": 9836,
"preview": "package filters\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containrrr/watchtower/pkg/container/mocks\"\n\t\"github.com/stretchr/test"
},
{
"path": "pkg/lifecycle/lifecycle.go",
"chars": 3442,
"preview": "package lifecycle\n\nimport (\n\t\"github.com/containrrr/watchtower/pkg/container\"\n\t\"github.com/containrrr/watchtower/pkg/typ"
},
{
"path": "pkg/metrics/metrics.go",
"chars": 3074,
"preview": "package metrics\n\nimport (\n\t\"github.com/containrrr/watchtower/pkg/types\"\n\t\"github.com/prometheus/client_golang/prometheus"
},
{
"path": "pkg/notifications/common_templates.go",
"chars": 1109,
"preview": "package notifications\n\nvar commonTemplates = map[string]string{\n\t`default-legacy`: \"{{range .}}{{.Message}}{{println}}{{"
},
{
"path": "pkg/notifications/email.go",
"chars": 2081,
"preview": "package notifications\n\nimport (\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\n\tshoutrrrSmtp \"github.com/containrrr/shoutrrr/pkg/se"
},
{
"path": "pkg/notifications/gotify.go",
"chars": 2002,
"preview": "package notifications\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n\n\tshoutrrrGotify \"github.com/containrrr/shoutrrr/pkg/services/goti"
},
{
"path": "pkg/notifications/json.go",
"chars": 1540,
"preview": "package notifications\n\nimport (\n\t\"encoding/json\"\n\n\tt \"github.com/containrrr/watchtower/pkg/types\"\n)\n\ntype jsonMap = map["
},
{
"path": "pkg/notifications/json_test.go",
"chars": 2738,
"preview": "package notifications\n\nimport (\n\ts \"github.com/containrrr/watchtower/pkg/session\"\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github"
},
{
"path": "pkg/notifications/model.go",
"chars": 384,
"preview": "package notifications\n\nimport (\n\tt \"github.com/containrrr/watchtower/pkg/types\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\n// "
},
{
"path": "pkg/notifications/msteams.go",
"chars": 1119,
"preview": "package notifications\n\nimport (\n\t\"net/url\"\n\n\tshoutrrrTeams \"github.com/containrrr/shoutrrr/pkg/services/teams\"\n\tt \"githu"
},
{
"path": "pkg/notifications/notifications_suite_test.go",
"chars": 292,
"preview": "package notifications_test\n\nimport (\n\t\"github.com/onsi/gomega/format\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github"
},
{
"path": "pkg/notifications/notifier.go",
"chars": 3830,
"preview": "package notifications\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tty \"github.com/containrrr/watchtower/pkg/types\"\n\tlog \"github."
},
{
"path": "pkg/notifications/notifier_test.go",
"chars": 12055,
"preview": "package notifications_test\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/containrrr/watchtower/cmd\"\n\t\"github.com/con"
},
{
"path": "pkg/notifications/preview/data/data.go",
"chars": 3403,
"preview": "package data\n\nimport (\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"math/rand\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/containrrr/watchtower/pkg"
},
{
"path": "pkg/notifications/preview/data/logs.go",
"chars": 1175,
"preview": "package data\n\nimport (\n\t\"time\"\n)\n\ntype logEntry struct {\n\tMessage string\n\tData map[string]any\n\tTime time.Time\n\tLev"
},
{
"path": "pkg/notifications/preview/data/preview_strings.go",
"chars": 4957,
"preview": "package data\n\nvar containerNames = []string{\n\t\"cyberscribe\",\n\t\"datamatrix\",\n\t\"nexasync\",\n\t\"quantumquill\",\n\t\"aerosphere\","
},
{
"path": "pkg/notifications/preview/data/report.go",
"chars": 2641,
"preview": "package data\n\nimport (\n\t\"sort\"\n\n\t\"github.com/containrrr/watchtower/pkg/types\"\n)\n\n// State is the outcome of a container "
},
{
"path": "pkg/notifications/preview/data/status.go",
"chars": 791,
"preview": "package data\n\nimport wt \"github.com/containrrr/watchtower/pkg/types\"\n\ntype containerStatus struct {\n\tcontainerID wt.Co"
},
{
"path": "pkg/notifications/preview/tplprev.go",
"chars": 744,
"preview": "package preview\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/containrrr/watchtower/pkg/notifications/previ"
},
{
"path": "pkg/notifications/shoutrrr.go",
"chars": 6302,
"preview": "package notifications\n\nimport (\n\t\"bytes\"\n\tstdlog \"log\"\n\t\"os\"\n\t\"strings\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/containrr"
},
{
"path": "pkg/notifications/shoutrrr_test.go",
"chars": 11354,
"preview": "package notifications\n\nimport (\n\t\"time\"\n\n\t\"github.com/containrrr/shoutrrr/pkg/types\"\n\t\"github.com/containrrr/watchtower/"
},
{
"path": "pkg/notifications/slack.go",
"chars": 2062,
"preview": "package notifications\n\nimport (\n\t\"strings\"\n\n\tshoutrrrDisco \"github.com/containrrr/shoutrrr/pkg/services/discord\"\n\tshoutr"
},
{
"path": "pkg/notifications/templates/funcs.go",
"chars": 550,
"preview": "package templates\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"golang.org/x/text/cases\"\n\t\"golang.org"
},
{
"path": "pkg/registry/auth/auth.go",
"chars": 4410,
"preview": "package auth\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/containrr"
},
{
"path": "pkg/registry/auth/auth_test.go",
"chars": 6357,
"preview": "package auth_test\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/containrrr/watchtower/in"
},
{
"path": "pkg/registry/digest/digest.go",
"chars": 3669,
"preview": "package digest\n\nimport (\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings"
},
{
"path": "pkg/registry/digest/digest_test.go",
"chars": 3543,
"preview": "package digest_test\n\nimport (\n\t\"fmt\"\n\t\"github.com/containrrr/watchtower/internal/actions/mocks\"\n\t\"github.com/containrrr/"
},
{
"path": "pkg/registry/helpers/helpers.go",
"chars": 665,
"preview": "package helpers\n\nimport (\n\t\"github.com/distribution/reference\"\n)\n\n// domains for Docker Hub, the default registry\nconst "
},
{
"path": "pkg/registry/helpers/helpers_test.go",
"chars": 1352,
"preview": "package helpers\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n)\n\nfunc TestHelpers(t *tes"
},
{
"path": "pkg/registry/manifest/manifest.go",
"chars": 1115,
"preview": "package manifest\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\turl2 \"net/url\"\n\n\t\"github.com/containrrr/watchtower/pkg/registry/helpers\"\n\t\""
},
{
"path": "pkg/registry/manifest/manifest_test.go",
"chars": 2526,
"preview": "package manifest_test\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/containrrr/watchtower/internal/actions/mocks\"\n\t\"github."
},
{
"path": "pkg/registry/registry.go",
"chars": 1877,
"preview": "package registry\n\nimport (\n\t\"github.com/containrrr/watchtower/pkg/registry/helpers\"\n\twatchtowerTypes \"github.com/contain"
},
{
"path": "pkg/registry/registry_suite_test.go",
"chars": 259,
"preview": "package registry_test\n\nimport (\n\t\"github.com/sirupsen/logrus\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/ons"
},
{
"path": "pkg/registry/registry_test.go",
"chars": 1507,
"preview": "package registry_test\n\nimport (\n\t\"github.com/containrrr/watchtower/internal/actions/mocks\"\n\tunit \"github.com/containrrr/"
},
{
"path": "pkg/registry/trust.go",
"chars": 3300,
"preview": "package registry\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/containrrr/watchtower/pkg/r"
},
{
"path": "pkg/registry/trust_test.go",
"chars": 1203,
"preview": "package registry\n\nimport (\n\t\"os\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar _ = Describe(\"Registry "
},
{
"path": "pkg/session/container_status.go",
"chars": 1831,
"preview": "package session\n\nimport wt \"github.com/containrrr/watchtower/pkg/types\"\n\n// State indicates what the current state is of"
},
{
"path": "pkg/session/progress.go",
"chars": 1788,
"preview": "package session\n\nimport (\n\t\"github.com/containrrr/watchtower/pkg/types\"\n)\n\n// Progress contains the current session cont"
},
{
"path": "pkg/session/report.go",
"chars": 3019,
"preview": "package session\n\nimport (\n\t\"sort\"\n\n\t\"github.com/containrrr/watchtower/pkg/types\"\n)\n\ntype report struct {\n\tscanned []type"
},
{
"path": "pkg/sorter/sort.go",
"chars": 2677,
"preview": "package sorter\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/containrrr/watchtower/pkg/types\"\n)\n\n// ByCreated allows a list of "
},
{
"path": "pkg/types/container.go",
"chars": 1920,
"preview": "package types\n\nimport (\n\t\"strings\"\n\n\t\"github.com/docker/docker/api/types\"\n\tdc \"github.com/docker/docker/api/types/contai"
},
{
"path": "pkg/types/convertible_notifier.go",
"chars": 365,
"preview": "package types\n\nimport (\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n)\n\n// ConvertibleNotifier is a notifier capable of creating a"
},
{
"path": "pkg/types/filter.go",
"chars": 200,
"preview": "package types\n\n// A Filter is a prototype for a function that can be used to filter the\n// results from a call to the Li"
},
{
"path": "pkg/types/filterable_container.go",
"chars": 239,
"preview": "package types\n\n// A FilterableContainer is the interface which is used to filter\n// containers.\ntype FilterableContainer"
},
{
"path": "pkg/types/notifier.go",
"chars": 229,
"preview": "package types\n\n// Notifier is the interface that all notification services have in common\ntype Notifier interface {\n\tSta"
},
{
"path": "pkg/types/registry_credentials.go",
"chars": 199,
"preview": "package types\n\n// RegistryCredentials is a credential pair used for basic auth\ntype RegistryCredentials struct {\n\tUserna"
},
{
"path": "pkg/types/report.go",
"chars": 566,
"preview": "package types\n\n// Report contains reports for all the containers processed during a session\ntype Report interface {\n\tSca"
},
{
"path": "pkg/types/token_response.go",
"chars": 148,
"preview": "package types\n\n// TokenResponse is returned by the registry on successful authentication\ntype TokenResponse struct {\n\tTo"
},
{
"path": "pkg/types/update_params.go",
"chars": 364,
"preview": "package types\n\nimport (\n\t\"time\"\n)\n\n// UpdateParams contains all different options available to alter the behavior of the"
},
{
"path": "prometheus/prometheus.yml",
"chars": 193,
"preview": "scrape_configs:\n - job_name: watchtower\n scrape_interval: 5s\n metrics_path: /v1/metrics\n bearer_token: demotok"
},
{
"path": "scripts/build-tplprev.sh",
"chars": 179,
"preview": "#!/bin/bash\n\ncd $(git rev-parse --show-toplevel)\n\ncp \"$(go env GOROOT)/misc/wasm/wasm_exec.js\" ./docs/assets/\n\nGOARCH=wa"
},
{
"path": "scripts/codecov.sh",
"chars": 158,
"preview": "#!/usr/bin/env bash\n\ngo test -v -coverprofile coverage.out -covermode atomic ./...\n\n# Requires CODECOV_TOKEN to be set\nb"
},
{
"path": "scripts/contnet-tests.sh",
"chars": 1336,
"preview": "#!/usr/bin/env bash\n\nset -e\n\nfunction exit_env_err() {\n >&2 echo \"Required environment variable not set: $1\"\n exit 1\n}"
},
{
"path": "scripts/dependency-test.sh",
"chars": 2986,
"preview": "#!/usr/bin/env bash\n\n# Simulates a container that will always be updated, checking whether it shuts down it's dependenci"
},
{
"path": "scripts/docker-util.sh",
"chars": 4171,
"preview": "#!/usr/bin/env bash\n# This file is meant to be sourced into other scripts and contain some utility functions for docker "
},
{
"path": "scripts/du-cli.sh",
"chars": 1576,
"preview": "#!/usr/bin/env bash\n\nSCRIPT_ROOT=$(dirname \"$(readlink -m \"$(type -p \"$0\")\")\")\nsource \"$SCRIPT_ROOT/docker-util.sh\"\n\ncas"
},
{
"path": "scripts/lifecycle-tests.sh",
"chars": 6051,
"preview": "#!/usr/bin/env bash\n\nset -e\n\nIMAGE=server\nCONTAINER=server\nLINKED_IMAGE=linked\nLINKED_CONTAINER=linked\nWATCHTOWER_INTERV"
},
{
"path": "tplprev/main.go",
"chars": 1138,
"preview": "//go:build !wasm\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/containrrr/watchtower/internal/meta\"\n\t\"githu"
},
{
"path": "tplprev/main_wasm.go",
"chars": 1286,
"preview": "//go:build wasm\n\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/containrrr/watchtower/internal/meta\"\n\t\"github.com/containrr"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the containrrr/watchtower GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 165 files (19.7 MB), approximately 176.6k tokens, and a symbol index with 500 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.