This guide provides a full Ansible playbook to deploy Inventree with Docker Compose on Debian 10+, Ubuntu LTS, and RHEL 9+ compatible hosts. The current stable version is 1.2.0, released February 12, 2026.
---
- name: Deploy InvenTree
hosts: inventree
become: true
vars:
app_root: /opt/inventree
app_port: 8000
inventree_version: "stable" # Use "stable" for production, "latest" for development
postgres_db: inventree
postgres_user: inventree
postgres_password: inventree123 # Change this to a strong password
inventree_secret_key: "change-this-to-a-very-long-random-string"
tasks:
- name: Install Docker on Debian/Ubuntu
apt:
name:
- docker.io
- docker-compose-plugin
state: present
update_cache: true
when: ansible_os_family == "Debian"
notify: restart docker
- name: Install Docker on RHEL family
dnf:
name:
- docker
- docker-compose-plugin
state: present
when: ansible_os_family == "RedHat"
notify: restart docker
- name: Enable and start Docker
service:
name: docker
state: started
enabled: true
- name: Add docker group
group:
name: docker
state: present
- name: Add ansible user to docker group
user:
name: "{{ ansible_user }}"
groups: docker
append: yes
when: ansible_user is defined
- name: Create application directory
file:
path: "{{ app_root }}"
state: directory
owner: "{{ ansible_user | default('root') }}"
mode: "0755"
- name: Create Docker Compose file
template:
src: docker-compose.yml.j2
dest: "{{ app_root }}/docker-compose.yml"
owner: "{{ ansible_user | default('root') }}"
mode: "0644"
- name: Create environment file
template:
src: inventree.env.j2
dest: "{{ app_root }}/.env"
owner: "{{ ansible_user | default('root') }}"
mode: "0600"
- name: Start application stack
docker_compose:
project_src: "{{ app_root }}"
state: present
pull: yes
register: compose_result
- name: Wait for InvenTree to be ready
wait_for:
port: "{{ app_port }}"
host: "{{ ansible_host | default(inventory_hostname) }}"
delay: 30
timeout: 300
when: compose_result.changed
handlers:
- name: restart docker
service:
name: docker
state: restarted
Create the following template files in your Ansible roles/templates directory:
templates/docker-compose.yml.j2:
version: '3.8'
services:
db:
image: postgres:15
restart: always
environment:
POSTGRES_DB: {{ postgres_db }}
POSTGRES_USER: {{ postgres_user }}
POSTGRES_PASSWORD: {{ postgres_password }}
volumes:
- inventree-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U {{ postgres_user }} -d {{ postgres_db }}"]
interval: 30s
timeout: 10s
retries: 5
redis:
image: redis:alpine
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 5
inventree-server:
image: ghcr.io/inventree/inventree:{{ inventree_version }}
restart: always
depends_on:
- db
- redis
ports:
- "{{ app_port }}:8000"
environment:
- INVENTREE_DB_ENGINE=postgresql
- INVENTREE_DB_NAME={{ postgres_db }}
- INVENTREE_DB_USER={{ postgres_user }}
- INVENTREE_DB_PASSWORD={{ postgres_password }}
- INVENTREE_DB_HOST=db
- INVENTREE_REDIS_URL=redis://redis:6379
- INVENTREE_MEDIA_ROOT=/media
- INVENTREE_STATIC_ROOT=/static
- INVENTREE_SECRET_KEY={{ inventree_secret_key }}
- INVENTREE_ALLOWED_HOSTS=* # Configure properly for production
volumes:
- inventree-media-data:/media
- inventree-static-data:/static
command: >
sh -c "python manage.py migrate &&
python manage.py collectstatic --no-input &&
python manage.py check --deploy &&
gunicorn InvenTree.wsgi:application --bind 0.0.0.0:8000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/"]
interval: 30s
timeout: 10s
retries: 5
volumes:
inventree-db-data:
inventree-media-data:
inventree-static-data:
templates/inventree.env.j2:
INVENTREE_DB_ENGINE=postgresql
INVENTREE_DB_NAME={{ postgres_db }}
INVENTREE_DB_USER={{ postgres_user }}
INVENTREE_DB_PASSWORD={{ postgres_password }}
INVENTREE_DB_HOST=db
INVENTREE_REDIS_URL=redis://redis:6379
INVENTREE_MEDIA_ROOT=/media
INVENTREE_STATIC_ROOT=/static
INVENTREE_SECRET_KEY={{ inventree_secret_key }}
INVENTREE_ALLOWED_HOSTS=*
INVENTREE_BASE_URL=http://{{ ansible_host | default(inventory_hostname) }}:{{ app_port }}
For production deployments, configure a reverse proxy like nginx:
- name: Install nginx
package:
name: nginx
state: present
- name: Create nginx configuration
template:
src: inventree.nginx.conf.j2
dest: /etc/nginx/sites-available/inventree
mode: "0644"
- name: Enable nginx site
file:
src: /etc/nginx/sites-available/inventree
dest: /etc/nginx/sites-enabled/inventree
state: link
- name: Remove default nginx site
file:
path: /etc/nginx/sites-enabled/default
state: absent
- name: Restart nginx
service:
name: nginx
state: restarted
enabled: yes
Template for templates/inventree.nginx.conf.j2:
server {
listen 80;
server_name {{ inventree_domain | default('inventree.example.com') }};
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ {
alias /opt/inventree/static/;
expires 30d;
}
location /media/ {
alias /opt/inventree/media/;
expires 30d;
}
}
Add backup tasks to your playbook:
- name: Create backup directory
file:
path: /opt/inventree/backups
state: directory
mode: "0700"
- name: Create backup script
copy:
content: |
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/opt/inventree/backups"
# Backup database
docker exec inventree-db pg_dump -U {{ postgres_user }} {{ postgres_db }} > "$BACKUP_DIR/inventree_$DATE.sql"
# Backup media files
docker run --rm -v inventree-media-data:/data -v "$BACKUP_DIR:/backup" alpine tar czf "/backup/media_$DATE.tar.gz" -C /data .
# Clean backups older than 30 days
find "$BACKUP_DIR" -name "*.sql" -mtime +30 -delete
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete
dest: /opt/inventree/backup.sh
mode: "0700"
inventree_version: "stable" for the latest stable release (1.2.0)inventree_version: "latest" for the most recent featuresinventree_version: "1.2.0" for pinned deploymentsAny questions?
Feel free to contact us. Find all contact information on our contact page.