- This project automates the deployment and ongoing administration of ADempiere ERP on Linux VPS servers using Ansible.
- It covers the full lifecycle: OS hardening, Docker installation, ADempiere container stack deployment, database restores, OS updates, and more β from a freshly provisioned server to a running production instance, and every day-2 task thereafter.
Traefik (FrontEnd / HTTPS) is optional and not yet fully implemented.
ADempiere runs completely without it. The BackEnd deployment (deploy-backend.sh) works end-to-end.
The FrontEnd (Traefik reverse proxy + Let's Encrypt TLS) is partially implemented β key pieces are missing before it can be used in production. See docs/traefik-status.md for the current state and open tasks.
- Control node
- The scenario
- Benefits
- Ansible building blocks
- Quick start
- Scripts and playbooks
- Documentation
- License
The control node is your own workstation or laptop β the machine from which you run all commands.
It is not a server being deployed. Nothing runs on it permanently once the deployment is complete.
Ansible connects outbound from the control node to the servers over SSH and executes tasks remotely.
The servers never initiate contact or pull configuration.
All operator-specific files live on the control node: group_vars/all/vars.yml, group_vars/all/vault.yml, the SSH keypair (ssh_keys/), the vault password file (~/.vault_pass.txt), and all deployment logs (logs/). None of these are committed to git.
The control node requires only Ansible (core 2.14+) and three collections (community.docker, community.postgresql, community.crypto). No Docker, no ADempiere, no server software.
flowchart TB
CN["π₯ Control Node\n(your local machine)\nansible-playbook\ndeploy-backend.sh / restore-db.sh"]
Internet(["π Internet"])
subgraph FE["FrontEnd VPS"]
TR["Traefik :80 / :443\nLet's Encrypt TLS"]
end
subgraph BE["BackEnd VPS"]
ADE["ADempiere ERP"]
PG["PostgreSQL"]
end
CF["Cloudflare DNS"]
CN -->|"SSH (deploy)"| BE
CN -->|"SSH (deploy)"| FE
Internet -->|"HTTPS"| FE
TR -->|"HTTP (internal)"| ADE
TR <-->|"DNS-01 challenge"| CF
- You are working on your local machine (the control node).
- You have two Linux VPS servers β one serves as the application backend, one as the public-facing frontend.
- For a first-time setup, the servers need to be reachable via SSH. Root access is required for the initial hardening steps; subsequent steps use a dedicated non-root user.
FrontEnd VPS (<frontend_ip>)
Your local machine ββββ SSH βββββΊ Public-facing server.
β Runs Traefik: receives internet traffic,
β terminates HTTPS, forwards to BackEnd.
β β
β β HTTP (internal)
β βΌ
β BackEnd VPS (<backend_ip>)
βββββ SSH βββββΊ Application server.
Runs ADempiere ERP + PostgreSQL database.
β Also directly reachable from the internet
unless the hosting provider's firewall
restricts access. No firewall is configured
by this project.
After a full BackEnd deployment, the following will be in place:
- Both servers are hardened: SSH runs on a custom port, root login is disabled, only key-based authentication is allowed, automatic security updates are enabled.
- Both servers have Docker CE installed.
- The BackEnd server runs the ADempiere ERP container stack (application + PostgreSQL database).
- Optionally: the FrontEnd server runs Traefik, a reverse proxy that terminates HTTPS using a Let's Encrypt certificate issued via the Cloudflare DNS API and forwards requests to the BackEnd. See docs/traefik-status.md before enabling this.
Configuration is split across two gitignored files under group_vars/all/:
vars.ymlβ plain-text deployment values (SSH port, username, key path). Copy fromgroup_vars/vars_template.yml.vault.ymlβ AES-256 encrypted secrets (passwords, API tokens). Copy fromgroup_vars/vault_template.ymland encrypt withansible-vault encrypt.
The templates live one level up in group_vars/ (not inside all/) because Ansible auto-loads every .yml file it finds in group_vars/all/ β placing templates there would cause their placeholder values to override your real credentials.
The vault password must be stored in ~/.vault_pass.txt on the control node; ansible.cfg references this file so Ansible decrypts the vault automatically on every run.
Change the vault password before deploying to production. See docs/vault.md.
You run all commands from your local machine.
Ansible connects to the servers over SSH and handles everything remotely.
-
One-command server setup β
./deploy-backend.shtakes a freshly provisioned VPS and fully deploys ADempiere in a single step: OS hardening, swap, Docker, the full ADempiere container stack, and crontab β no manual steps in between. -
Hardening out of the box β every server gets SSH port customization, key-based authentication only, root login disabled, MaxAuthTries enforced, modern cipher suites, and automatic unattended security upgrades β applied consistently to every deployment without any manual configuration.
-
Not just a one-time installer β a full administration toolkit β the same Ansible playbooks that deploy the server are also used day-to-day: apply OS updates, rotate SSH keys, update ADempiere to a new version, force a clean container restart, restore a database, or re-run any individual step in isolation. See docs/operations.md.
-
Database restore in two steps β download a backup to your workstation, set two variables in
vars.yml, and run./restore-db.sh. The script displays all restore parameters and asks for explicit confirmation before touching anything. -
Automatic health verification β after every fresh deployment,
health-check.shruns automatically and checks every container and HTTP endpoint. The play fails immediately if anything is unhealthy, so a broken deployment is never silently accepted. -
Dry-run before you commit β
./deploy-backend.sh --checkshows exactly what would change on the server without making a single write. -
Idempotent and self-correcting β every playbook is safe to re-run at any time. If a step fails halfway through, fix the issue and re-run: steps that already succeeded report
ok, pending steps pick up where they left off. -
Repeatable across environments β the same playbooks deploy to development, staging, and production. Per-environment differences live entirely in gitignored
vars.ymlandvault.yml; nothing environment-specific is ever committed. -
Transparent by design β both entry-point scripts display a full configuration summary β target server, all settings, vault variable status β before any confirmation prompt, so the operator knows exactly what will run before typing
YES. -
Seamless server resets β before connecting to any BackEnd server,
deploy-backend.shautomatically removes the old SSH host fingerprint from~/.ssh/known_hosts. Reinstalling a server no longer requires a manualssh-keygen -Rstep β the script handles it silently every time. -
All configuration in version control β deployment logic, role defaults, and templates are committed; secrets are AES-256 encrypted in
vault.yml. The exact deployment is fully reproducible at any point in time.
New to Ansible? See docs/technologies.md for a short introduction to playbooks and roles with a concrete example before reading on.
Ansible projects are built from a small set of composable concepts. Here is how they relate to each other:
Control Node (your local machine)
β
βββ ansible.cfg β global settings: inventory path, vault password file
β
βββ inventories/hosts β list of target servers, organised into named groups
β
βββ group_vars/ β variables shared across a group of hosts
β βββ vars_template.yml β reference template for all/vars.yml β committed
β βββ vault_template.yml β reference template for all/vault.yml β committed
β βββ all/ β Ansible auto-loads every .yml file here
β βββ vars.yml β plain-text config values (SSH port, username) β gitignored
β βββ vault.yml β AES-256 encrypted secrets (passwords) β gitignored
β
βββ Playbook (*.yml) β entry point: "run these roles on these hosts"
β βββ hosts: <group> β which inventory group to target
β βββ become: true/false β whether to escalate privileges (sudo)
β βββ pre_tasks: β steps that run before roles (e.g. set connection vars)
β βββ roles: [role-a, role-b] β delegates work to one or more roles
β
βββ roles/<name>/ β self-contained, reusable unit of work
βββ tasks/main.yml β the steps to execute (the "what")
βββ defaults/main.yml β lowest-priority variable defaults (always overridable)
βββ vars/main.yml β higher-priority role constants
βββ templates/*.j2 β Jinja2 templates β rendered with variables, copied to server
βββ files/ β static files copied to the server as-is
βββ handlers/main.yml β triggered by notify: directives (e.g. restart SSH)
βββ meta/main.yml β role metadata and inter-role dependencies
Variable precedence (highest wins):
CLI -e "key=value" β highest β always overrides everything
β
βΌ
roles/<name>/vars/main.yml β role-level constants
β
βΌ
group_vars/all/vars.yml β config values (domain, port, usernameβ¦)
β
βΌ
group_vars/all/vault.yml β encrypted secrets (passwords, API tokens)
β
βΌ
roles/<name>/defaults/main.yml β lowest β safe defaults, meant to be overridden
For the detailed relationships between the specific playbooks, roles, and inventory groups in this project, see docs/relationships.md.
One-time setup (do this once after cloning):
# 1. Install required Ansible collections
ansible-galaxy collection install community.docker community.postgresql community.crypto
# 2. Create the vault password file
echo "YourVaultPassword" > ~/.vault_pass.txt && chmod 600 ~/.vault_pass.txt
# 3. Configure your deployment
cp group_vars/vars_template.yml group_vars/all/vars.yml # fill in IPs, domain, SSH port
cp group_vars/vault_template.yml group_vars/all/vault.yml # fill in passwords and tokens
ansible-vault encrypt group_vars/all/vault.yml
cp inventories/hosts_template.yml inventories/hosts.yml # fill in server IPsCheck your configuration first (reads and validates everything β no changes made):
./check-config.sh deploy-backendDeploy a BackEnd server (fresh server, port 22, root access):
./deploy-backend.shRestore the database (after deploy, if needed):
./restore-db.shFor the full walkthrough including dry runs and verification steps, see docs/getting-started.md.
Two entry-point scripts cover the two most common operations:
| Script | When to use it |
|---|---|
./check-config.sh deploy-backend |
Pre-flight check β run this first. Reads and validates all variables, vault secrets, inventory, and SSH keypair without touching any server. Ends with a clear CAN run / CANNOT run verdict. |
./check-config.sh restore-db |
Pre-flight check for restore-db.sh β validates restore variables and confirms the backup file exists on the control node. |
./deploy-backend.sh |
Full BackEnd provisioning from a clean server. Handles keypair setup, pre-flight checks, and runs all playbooks in order with safety prompts and logging. |
./restore-db.sh |
Upload a PostgreSQL backup from the control node and restore it into a running ADempiere stack. |
# Pre-flight check β validates all configuration without touching the server
./check-config.sh deploy-backend
# Dry run β shows what would change, no writes
./deploy-backend.sh --check
# Live run β provisions the BackEnd server
./deploy-backend.sh
# Database restore (set restore variables in vars.yml first)
./restore-db.shBoth scripts write a timestamped log to logs/ on the control node.
After the initial deployment, the same Ansible playbooks handle all ongoing administration tasks. Run any of them independently from the control node:
| Task | Command |
|---|---|
| Apply OS security updates (reboots automatically if needed) | ansible-playbook os-updates.yml |
| Update ADempiere to the latest commit on the configured branch | ansible-playbook deploy-adempiere.yml |
| Force a full ADempiere container restart (stop all first, then re-run) | SSH to server β stop-all.sh, then ansible-playbook deploy-adempiere.yml |
| Add or rotate an admin SSH key | Add .pub to roles/serversconf/files/public_keys/present/admin/, then ansible-playbook serversconf.yml --start-at-task "Add ADMIN ssh-keys" |
| Check container health | SSH to server β health-check.sh |
For the full operations reference, see docs/operations.md.
For a step-by-step breakdown of each script, expected output, log location, re-run behaviour after a partial failure, and common failure modes, see docs/running.md.
For example output from a real successful run, see docs/demo.md.
| Topic | File |
|---|---|
| Getting started | |
| Getting started β deployment timeline + walkthrough | docs/getting-started.md |
| Installation β step by step | docs/installation.md |
| Running the system & playbook reference | docs/running.md |
| Demo β real deployment output | docs/demo.md |
| Architecture & design | |
| Architecture & network layout | docs/architecture.md |
| How it works β runtime behaviour | docs/how-it-works.md |
| Technologies: Ansible, Traefik, Docker | docs/technologies.md |
| File relationships β playbooks, roles, inventory | docs/relationships.md |
| Project structure | docs/project-structure.md |
| Configuration & secrets | |
| Complete variable reference | docs/variables.md |
| Configuration reference | docs/configuration.md |
| Vault management | docs/vault.md |
| Security notes | docs/security.md |
| Operations & maintenance | |
| Operations & day-2 tasks | docs/operations.md |
| Testing & debugging guide | docs/testing.md |
| Debugging & troubleshooting | docs/troubleshooting.md |
| Known issues & technical debt | docs/known-issues.md |
| Demos & status | |
| Traefik FrontEnd β status & contribution guide | docs/traefik-status.md |
| Reference | |
| System requirements | docs/requirements.md |
| Files explained β per-file deep dives | docs/files-explained.md |
MIT-0 β See SPDX