├── .github └── workflows │ ├── ci.yaml │ └── release.yaml ├── .yamllint ├── LICENSE ├── README.md ├── defaults └── main.yml ├── meta └── main.yml ├── molecule └── default │ ├── converge.yml │ └── molecule.yml └── tasks ├── containers.yml ├── main.yml ├── networks.yml └── registries.yml /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | "on": 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | defaults: 10 | run: 11 | working-directory: "hadret.containers" 12 | 13 | jobs: 14 | lint: 15 | name: Lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Check out the codebase. 19 | uses: actions/checkout@v2 20 | with: 21 | path: "hadret.containers" 22 | 23 | - name: Set up Python 3. 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: "3.x" 27 | 28 | - name: Install test dependencies. 29 | run: pip3 install yamllint 30 | 31 | - name: Lint code. 32 | run: | 33 | yamllint . 34 | 35 | molecule: 36 | name: Molecule 37 | runs-on: ubuntu-latest 38 | strategy: 39 | matrix: 40 | distro: 41 | - ubuntu2004 42 | - debian11 43 | 44 | steps: 45 | - name: Check out the codebase. 46 | uses: actions/checkout@v2 47 | with: 48 | path: "hadret.containers" 49 | 50 | - name: Set up Python 3. 51 | uses: actions/setup-python@v2 52 | with: 53 | python-version: "3.x" 54 | 55 | - name: Install test dependencies. 56 | run: pip3 install ansible molecule[docker] docker 57 | 58 | - name: Run Molecule tests. 59 | run: molecule test 60 | env: 61 | PY_COLORS: "1" 62 | ANSIBLE_FORCE_COLOR: "1" 63 | MOLECULE_DISTRO: ${{ matrix.distro }} 64 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This workflow requires a GALAXY_API_KEY secret present in the GitHub 3 | # repository or organization. 4 | # 5 | # See: https://github.com/marketplace/actions/publish-ansible-role-to-galaxy 6 | # See: https://github.com/ansible/galaxy/issues/46 7 | 8 | name: Release 9 | "on": 10 | push: 11 | tags: 12 | - "*" 13 | 14 | defaults: 15 | run: 16 | working-directory: "hadret.containers" 17 | 18 | jobs: 19 | release: 20 | name: Release 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Check out the codebase. 24 | uses: actions/checkout@v2 25 | with: 26 | path: "hadret.containers" 27 | 28 | - name: Set up Python 3. 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: "3.x" 32 | 33 | - name: Install Ansible. 34 | run: pip3 install ansible-core 35 | 36 | - name: Trigger a new import on Galaxy. 37 | run: >- 38 | ansible-galaxy role import --api-key ${{ secrets.GALAXY_API_KEY }} 39 | $(echo ${{ github.repository }} | cut -d/ -f1) $(echo ${{ github.repository }} | cut -d/ -f2) 40 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: 6 | max: 120 7 | level: warning 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Filip Chabik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ansible Role: Containers 2 | ======================== 3 | 4 | [![CI](https://github.com/hadret/ansible-role-containers/actions/workflows/ci.yaml/badge.svg)](https://github.com/hadret/ansible-role-containers/actions/workflows/ci.yaml) 5 | 6 | 7 | An Ansible role that spins up an array of [Docker](https://docker.com) 8 | containers on Linux. It can additionally handle arrays of Docker registries 9 | and networks. 10 | 11 | Requirements 12 | ------------ 13 | 14 | Docker needs to be in place in order for this role to work. Additionally, python 15 | module [docker](https://pypi.org/project/docker) is also needed (i.e. Docker 16 | SDK for Python). 17 | 18 | Role Variables 19 | -------------- 20 | 21 | All available variables are arrays (`containers`, `networks` and `registries`). 22 | They are empty by default but you can find one example for each in 23 | [defaults/main.yml](defaults/main.yml). 24 | 25 | Dependencies 26 | ------------ 27 | 28 | The following roles are not a hard dependencies, hence they are not mentioned 29 | in the [meta/main.yml](meta/main.yml) file. Reason for that geerlingguy.docker 30 | and geerlingguy.pip is to just ensure that the Docker daemon and Docker Python 31 | SDK is present (as both of these are hard dependencies). 32 | 33 | - [geerlingguy.pip](https://github.com/geerlingguy/ansible-role-pip) 34 | - [geerlingguy.docker](https://github.com/geerlingguy/ansible-role-docker) 35 | 36 | Example Playbook 37 | ---------------- 38 | 39 | ``` 40 | - hosts: all 41 | 42 | vars: 43 | pip_package: python-pip 44 | pip_install_packages: 45 | - name: docker 46 | 47 | networks: 48 | - name: network-1 49 | 50 | containers: 51 | - name: hello-1 52 | image: "hello-world" 53 | state: started 54 | restart_policy: always 55 | networks: 56 | - name: network-1 57 | - name: hello-2 58 | image: "hello-world" 59 | state: started 60 | restart_policy: always 61 | networks: 62 | - name: network-1 63 | 64 | roles: 65 | - geerlingguy.pip 66 | - geerlingguy.docker 67 | - hadret.containers 68 | ``` 69 | 70 | Credits 71 | ------- 72 | 73 | All of the `molecule` tests and CI configuration are based on work of 74 | [geerlingguy](https://github.com/geerlingguy). 75 | 76 | License 77 | ------- 78 | 79 | MIT 80 | 81 | Author Information 82 | ------------------ 83 | 84 | This role was somewhat assembled in 2019 by [Filip Chabik](https://chabik.com). 85 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | containers: [] 3 | # containers: 4 | # - name: cadvisor 5 | # image: "google/cadvisor:latest" 6 | # state: started 7 | # restart_policy: always 8 | # privileged: true 9 | # log_driver: journald 10 | # log_options: 11 | # tag: docker/cadvisor 12 | # published_ports: 13 | # - "127.0.0.1:8800:8080" 14 | # devices: 15 | # - "/dev/zfs:/dev/zfs" 16 | # volumes: 17 | # - "/:/rootfs:ro" 18 | # - "/var/run:/var/run:rw" 19 | # - "/sys:/sys:ro" 20 | # - "/var/lib/docker/:/var/lib/docker:ro" 21 | 22 | networks: [] 23 | # networks: 24 | # - name: ipv6_network 25 | # enable_ipv6: yes 26 | # ipam_config: 27 | # - subnet: fd00:0001:0001::/48 28 | 29 | registries: [] 30 | # registries: 31 | # - username: user 32 | # password: pass 33 | # registry_url: registry.docker.com 34 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependencies: [] 3 | 4 | galaxy_info: 5 | role_name: containers 6 | author: hadret 7 | description: Wrapper role on top of docker_container, docker_network and docker_login. 8 | license: MIT 9 | min_ansible_version: 2.7 10 | platforms: 11 | - name: Debian 12 | versions: 13 | - all 14 | - name: Ubuntu 15 | versions: 16 | - bionic 17 | - focal 18 | - jammy 19 | galaxy_tags: 20 | - containers 21 | - docker 22 | - networks 23 | - registry 24 | 25 | allow_duplicates: true 26 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: test all the things 3 | hosts: all 4 | become: true 5 | roles: 6 | - role: hadret.containers 7 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | role_name_check: 1 3 | dependency: 4 | name: galaxy 5 | driver: 6 | name: docker 7 | platforms: 8 | - name: instance 9 | image: 'geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2004}-ansible:latest' 10 | command: ${MOLECULE_DOCKER_COMMAND:-""} 11 | volumes: 12 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 13 | privileged: true 14 | pre_build_image: true 15 | provisioner: 16 | name: ansible 17 | playbooks: 18 | converge: ${MOLECULE_PLAYBOOK:-converge.yml} 19 | -------------------------------------------------------------------------------- /tasks/containers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: deploy the container(s) 3 | docker_container: 4 | name: "{{ item.name }}" 5 | api_version: '{{ item.api_version | default("auto") }}' 6 | auto_remove: '{{ item.auto_remove | default("no") }}' 7 | blkio_weight: "{{ item.blkio_weight | default(omit) }}" 8 | ca_cert: "{{ item.ca_cert | default(omit) }}" 9 | cap_drop: "{{ item.cap_drop | default(omit) }}" 10 | capabilities: "{{ item.capabilities | default(omit) }}" 11 | cleanup: '{{ item.cleanup | default("no") }}' 12 | client_cert: "{{ item.client_cert | default(omit) }}" 13 | client_key: "{{ item.client_key | default(omit) }}" 14 | command: "{{ item.command | default(omit) }}" 15 | comparisons: "{{ item.comparisons | default(omit) }}" 16 | cpu_period: "{{ item.cpu_period | default(omit) }}" 17 | cpu_quota: "{{ item.cpu_quota | default(omit) }}" 18 | cpu_shares: "{{ item.cpu_shares | default(omit) }}" 19 | cpuset_cpus: "{{ item.cpuset_cpus | default(omit) }}" 20 | cpuset_mems: "{{ item.cpuset_mems | default(omit) }}" 21 | debug: '{{ item.debug | default("no") }}' 22 | detach: '{{ item.detach | default("yes") }}' 23 | device_read_bps: "{{ item.device_read_bps | default(omit) }}" 24 | device_read_iops: "{{ item.device_read_iops | default(omit) }}" 25 | device_write_bps: "{{ item.device_write_bps | default(omit) }}" 26 | device_write_iops: "{{ item.device_write_iops | default(omit) }}" 27 | devices: "{{ item.devices | default(omit) }}" 28 | dns_opts: "{{ item.dns_opts | default(omit) }}" 29 | dns_search_domains: "{{ item.dns_search_domains | default(omit) }}" 30 | dns_servers: "{{ item.dns_servers | default(omit) }}" 31 | docker_host: "{{ item.docker_host | default(omit) }}" 32 | domainname: "{{ item.domainname | default(omit) }}" 33 | entrypoint: "{{ item.e | default(omit) }}" 34 | env: "{{ item.env | default(omit) }}" 35 | env_file: "{{ item.env_file | default(omit) }}" 36 | etc_hosts: "{{ item.etc_hosts | default(omit) }}" 37 | exposed_ports: "{{ item.exposed_ports | default(omit) }}" 38 | force_kill: '{{ item.force_kill | default("no") }}' 39 | groups: "{{ item.groups | default(omit) }}" 40 | healthcheck: "{{ item.healthcheck | default(omit) }}" 41 | hostname: "{{ item.hostname | default(omit) }}" 42 | ignore_image: '{{ item.ignore_image | default("no") }}' 43 | image: "{{ item.image | default(omit) }}" 44 | init: '{{ item.init | default("no") }}' 45 | interactive: '{{ item.interactive | default("no") }}' 46 | ipc_mode: "{{ item.ipc_mode | default(omit) }}" 47 | keep_volumes: '{{ item.keep_volumes | default("yes") }}' 48 | kernel_memory: "{{ item.kernel_memory | default(omit) }}" 49 | kill_signal: "{{ item.kill_signal | default(omit) }}" 50 | labels: "{{ item.labels | default(omit) }}" 51 | links: "{{ item.links | default(omit) }}" 52 | log_driver: "{{ item.log_driver | default(omit) }}" 53 | log_options: "{{ item.log_options | default(omit) }}" 54 | mac_address: "{{ item.mac_address | default(omit) }}" 55 | memory: '{{ item.memory | default("0") }}' 56 | memory_reservation: "{{ item.memory_reservation | default(omit) }}" 57 | memory_swap: "{{ item.memory_swap | default(omit) }}" 58 | memory_swappiness: "{{ item.memory_swappiness | default(omit) }}" 59 | network_mode: "{{ item.network_mode | default(omit) }}" 60 | networks: "{{ item.networks | default(omit) }}" 61 | networks_cli_compatible: "{{ item.networks_cli_compatible | default(omit) }}" 62 | oom_killer: "{{ item.oom_killer | default(omit) }}" 63 | oom_score_adj: "{{ item.oom_score_adj | default(omit) }}" 64 | output_logs: '{{ item.output_logs | default("no") }}' 65 | paused: '{{ item.paused | default("no") }}' 66 | pid_mode: "{{ item.pid_mode | default(omit) }}" 67 | pids_limit: "{{ item.pids_limit | default(omit) }}" 68 | privileged: '{{ item.privileged | default("no") }}' 69 | published_ports: "{{ item.published_ports | default(omit) }}" 70 | pull: '{{ item.pull | default("no") }}' 71 | purge_networks: '{{ item.purge_networks | default("no") }}' 72 | read_only: '{{ item.read_only | default("no") }}' 73 | recreate: '{{ item.recreate | default("no") }}' 74 | restart: '{{ item.restart | default("no") }}' 75 | restart_policy: "{{ item.restart_policy | default(omit) }}" 76 | restart_retries: "{{ item.restart_retries | default(omit) }}" 77 | runtime: "{{ item.runtime | default(omit) }}" 78 | security_opts: "{{ item.security_opts | default(omit) }}" 79 | shm_size: "{{ item.shm_size | default(omit) }}" 80 | ssl_version: "{{ item.ssl_version | default(omit) }}" 81 | state: '{{ item.state | default("started") }}' 82 | stop_signal: "{{ item.stop_signal | default(omit) }}" 83 | stop_timeout: "{{ item.stop_timeout | default(omit) }}" 84 | sysctls: "{{ item.sysctls | default(omit) }}" 85 | timeout: '{{ item.timeout | default("60") }}' 86 | tls: '{{ item.tls | default("no") }}' 87 | tls_hostname: '{{ item.tls_hostname | default("localhost") }}' 88 | tmpfs: "{{ item.tmpfs | default(omit) }}" 89 | tty: "{{ item.tty | default(omit) }}" 90 | ulimits: "{{ item.ulimits | default(omit) }}" 91 | user: "{{ item.user | default(omit) }}" 92 | userns_mode: "{{ item.userns_mode | default(omit) }}" 93 | uts: "{{ item.uts | default(omit) }}" 94 | validate_certs: '{{ item.validate_certs | default("no") }}' 95 | volume_driver: "{{ item.volume_driver | default(omit) }}" 96 | volumes: "{{ item.volumes | default(omit) }}" 97 | volumes_from: "{{ item.volumes_from | default(omit) }}" 98 | working_dir: "{{ item.working_dir | default(omit) }}" 99 | no_log: true 100 | loop: "{{ containers }}" 101 | when: containers is defined 102 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: registries.yml 3 | when: registries | length > 0 4 | 5 | - include_tasks: networks.yml 6 | when: networks | length > 0 7 | 8 | - include_tasks: containers.yml 9 | when: containers | length > 0 10 | -------------------------------------------------------------------------------- /tasks/networks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: setup the network(s) 3 | docker_network: 4 | name: '{{ item.name }}' 5 | api_version: '{{ item.api_version | default("auto") }}' 6 | appends: '{{ item.appends | default("no") }}' 7 | attachable: '{{ item.attachable | default(omit) }}' 8 | ca_cert: '{{ item.ca_cert | default(omit) }}' 9 | client_cert: '{{ item.client_cert | default(omit) }}' 10 | client_key: '{{ item.client_key | default(omit) }}' 11 | connected: '{{ item.connected | default(omit) }}' 12 | debug: '{{ item.debug | default("no") }}' 13 | docker_host: '{{ item.docker_host | default(omit) }}' 14 | driver: '{{ item.driver | default(omit) }}' 15 | driver_options: '{{ item.driver_options | default(omit) }}' 16 | enable_ipv6: '{{ item.enable_ipv6 | default(omit) }}' 17 | force: '{{ item.force | default("no") }}' 18 | internal: '{{ item.internal | default(omit) }}' 19 | ipam_config: '{{ item.ipam_config | default(omit) }}' 20 | ipam_driver: '{{ item.ipam_driver | default(omit) }}' 21 | ipam_driver_options: '{{ item.ipam_driver_options | default(omit) }}' 22 | labels: '{{ item.labels | default(omit) }}' 23 | scope: '{{ item.scope | default(omit) }}' 24 | ssl_version: '{{ item.ssl_version | default(omit) }}' 25 | state: '{{ item.state | default("present") }}' 26 | timeout: '{{ item.timeout | default("60") }}' 27 | tls: '{{ item.tls | default("no") }}' 28 | tls_hostname: '{{ item.tls_hostname | default("localhost") }}' 29 | validate_certs: '{{ item.validate_certs | default("no") }}' 30 | no_log: true 31 | loop: "{{ networks }}" 32 | when: networks is defined 33 | -------------------------------------------------------------------------------- /tasks/registries.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: log into private registry 3 | docker_login: 4 | username: '{{ item.username }}' 5 | password: '{{ item.password }}' 6 | api_version: '{{ item.api_version | default("auto") }}' 7 | ca_cert: '{{ item.ca_cert | default(omit) }}' 8 | client_cert: '{{ item.client_cert | default(omit) }}' 9 | client_key: '{{ item.client_key | default(omit) }}' 10 | config_path: '{{ item.config_path | default(omit) }}' 11 | debug: '{{ item.debug | default("no") }}' 12 | docker_host: '{{ item.docker_host | default(omit) }}' 13 | email: '{{ item.email | default(omit) }}' 14 | reauthorize: '{{ item.reauthorize | default("no") }}' 15 | registry_url: '{{ item.registry_url | default(omit) }}' 16 | ssl_version: '{{ item.ssl_version | default(omit) }}' 17 | state: '{{ item.state | default("present") }}' 18 | timeout: '{{ item.timeout | default("60") }}' 19 | tls: '{{ item.tls | default("no") }}' 20 | tls_hostname: '{{ item.tls_hostname | default("localhost") }}' 21 | validate_certs: '{{ item.validate_certs | default(omit) }}' 22 | no_log: true 23 | loop: "{{ registries }}" 24 | when: registries is defined 25 | --------------------------------------------------------------------------------