AtroPIM on Ubuntu 24.04 on Azure User Guide
Overview
AtroPIM is an open source Product Information Management system built on the AtroCore data platform. It gives a business one authoritative record for every product: names and identifiers, a configurable attribute model organised into attribute panels, hierarchical categories, classifications that apply whole attribute sets in a single step, brands, channels and localised values, and the images and documents that belong to each article. Everything the browser interface can do is also available over a REST API, so the catalogue can sit between your suppliers, your ERP and the sales channels that consume the data.
This image runs AtroPIM 1.16.5 on AtroCore 2.3.10 with PHP 8.4 under PHP FPM, nginx on port 80 and PostgreSQL 16 as the database, which is the engine upstream recommends. The AtroCore job manager runs from a systemd timer that executes the same console.php cron command upstream documents, so scheduled jobs, import feeds and export feeds are processed without any further setup.
The installation wizard is already completed inside the image. A stock AtroCore installation ends at a browser setup page that accepts database credentials and creates the first administrator with no authentication at all, which is not something that should ever face the public internet. This image drives that wizard during the build over the loopback interface, so every VM you launch starts at a login screen rather than at an open installer, and AtroCore removes the installer routes entirely once the system reports itself installed.
A unique administrator password is generated on the first boot of every VM, along with a unique database password, a unique password salt, a unique encryption key and a unique application identifier. Upstream fixes the salt and the encryption key at installation time, which would otherwise make them identical on every VM launched from one image; this image replaces all of them per VM, and holds the application back from serving anything until it has. Backed by 24/7 cloudimg support.
What is included:
- AtroPIM 1.16.5 on AtroCore 2.3.10, pinned to exact stable releases and recorded in a
composer.lockthat ships with the image - The free Import, Import HTTP, Export and Export HTTP modules, so data can be moved in and out over files or HTTP with no additional licence
- PHP 8.4 FPM, nginx on port 80 and PostgreSQL 16 bound to the loopback interface only
- The AtroCore job manager driven by
atropim-cron.timer, which runs upstream's ownconsole.php cronevery 60 seconds - The installation wizard completed at build time and its routes de registered, so no VM ever exposes an unauthenticated installer
- A unique administrator password, database password, password salt, encryption key and application identifier generated on first boot and recorded in a root only file
- PHP FPM and the job runner held back by a first boot marker, so nothing serves the application until every secret has been replaced
- A small starter catalogue, one category, one attribute and three articles, so the interface opens onto working data
- 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point and matches upstream's own stated minimum: a measured idle appliance uses roughly 660 MiB of the 3.8 GiB available, with PHP FPM capped at five workers. Size up if you plan to import very large catalogues or run many concurrent editors. NSG inbound: allow 22/tcp from your management network and 80/tcp (and 443/tcp if you add TLS). AtroPIM is served over plain HTTP by default, so put your own domain and a trusted certificate in front of it before exposing it to the internet.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for AtroPIM by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create and Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name atropim \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
After the VM is created, open port 80 so you can reach the web interface:
az vm open-port --resource-group <your-rg> --name atropim --port 80 --priority 900
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
The first boot rotates every secret and writes a summary into the message of the day, so your first SSH session shows the AtroPIM URL and where the credentials file lives.
Step 4 - Confirm the services are running
AtroPIM runs as PHP FPM behind nginx, with PostgreSQL as its database and a systemd timer driving the AtroCore job manager. Confirm all four are active:
systemctl is-active postgresql@16-main.service php8.4-fpm.service nginx.service atropim-cron.timer
All four report active. The screenshot below also shows the exact release set read back out of the shipped composer.lock, and that only port 80 is reachable from outside the VM while PostgreSQL stays on the loopback interface.

Step 5 - Confirm AtroPIM is answering
The image exposes a lightweight health endpoint that answers from nginx, and the application itself behind it:
curl -s -o /dev/null -w 'health -> HTTP %{http_code}\n' http://127.0.0.1/healthz
curl -s -o /dev/null -w 'web UI -> HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'REST API -> HTTP %{http_code}\n' http://127.0.0.1/api/
Expected output:
health -> HTTP 200
web UI -> HTTP 200
REST API -> HTTP 401
The 401 on the API is correct and is the useful signal here: it is PHP rejecting an unauthenticated caller. A 502 in that position would mean nginx is up but PHP FPM is not.
The next screenshot shows the mechanism that makes that possible. PHP FPM and the job runner both carry a ConditionPathExists on a marker that first boot creates only after every secret has been rotated, so on a brand new VM they are skipped until the rotation has finished. It also shows that the installer routes are gone, because the system already reports itself installed.

Step 6 - Retrieve your administrator password
Every secret on this VM was generated during its first boot and written to a root only file:
sudo stat -c 'mode %a owner %U:%G' /root/atropim-credentials.txt
sudo grep -E '^ATROPIM_URL|^ATROPIM_ADMIN_USER' /root/atropim-credentials.txt
Expected output:
mode 600 owner root:root
ATROPIM_URL=http://<your-vm-public-ip>/
ATROPIM_ADMIN_USER=admin
To read the password itself:
sudo grep '^ATROPIM_ADMIN_PASSWORD=' /root/atropim-credentials.txt | cut -d= -f2-
The same file also records ATROPIM_DB_USER and ATROPIM_DB_PASSWORD for the PostgreSQL role AtroPIM uses.
Step 7 - Confirm the default logins are rejected
AtroCore evaluates a username and password at exactly one route, /api/userSession, which returns a session token used for every other call. That makes it the right place to prove no guessable credential works on your VM:
for D in admin password atrocore atropim 123456; do
printf 'admin / %-9s -> HTTP %s\n' "$D" \
"$(curl -s -o /dev/null -w '%{http_code}' -m 10 \
-H "Authorization-Token: $(printf 'admin:%s' "$D" | base64 -w0)" \
http://127.0.0.1/api/userSession)"
done
Expected output:
admin / admin -> HTTP 401
admin / password -> HTTP 401
admin / atrocore -> HTTP 401
admin / atropim -> HTTP 401
admin / 123456 -> HTTP 401

The two accounts shown alongside admin are AtroCore's internal system accounts. Neither holds a password, so neither can sign in.
Step 8 - Query the catalogue over the REST API
Sign in once to mint a session token, then use that token for every other call:
PW=$(sudo grep '^ATROPIM_ADMIN_PASSWORD=' /root/atropim-credentials.txt | cut -d= -f2-)
TOKEN=$(curl -fsS -m 30 \
-H "Authorization-Token: $(printf 'admin:%s' "$PW" | base64 -w0)" \
http://127.0.0.1/api/userSession \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["user"]["token"])')
AUTH="Authorization-Token: $(printf 'admin:%s' "$TOKEN" | base64 -w0)"
curl -s -H "$AUTH" 'http://127.0.0.1/api/Product?maxSize=10' \
| python3 -m json.tool | head -30
That returns the starter catalogue, and the same records are visible directly in PostgreSQL, which is the proof the application really is reading and writing the database on this VM rather than a cache:
sudo runuser -u postgres -- psql -P pager=off \
-c "SELECT number, name, status FROM product WHERE deleted = false ORDER BY number" atropim
Expected output:
number | name | status
----------+------------------+--------
SKU-0001 | Sample Product A | ready
SKU-0002 | Sample Product B | ready
SKU-0003 | Sample Product C | ready
(3 rows)

Step 9 - Open AtroPIM in your browser
Browse to http://<your-vm-public-ip>/. AtroPIM presents its sign in page.

Step 10 - Sign in
Sign in as admin with the password from Step 6. The dashboard opens with a Getting Started panel and the full list of entities the platform exposes.

Step 11 - Open the Products catalogue
Choose Products in the top navigation. The grid shows the three starter articles with their article numbers, names and status, and the category tree sits alongside it.

Step 12 - Open a product record
Click a product name to open its record. The detail view is organised into the panels that make up your data model: Master Data, State, Description, Taxonomy and the attribute panels you define.

Step 13 - Edit product information
Use Edit, or the inline pencil beside any field, to change a value and save it. The change is written straight to PostgreSQL and rendered back immediately.

Step 14 - Work with categories
Categories are hierarchical and are managed from the tree in the left panel or from the Categories view. Products can belong to several categories, and category structure is what most downstream channels consume.

Step 15 - Shape the attribute model
Attributes are the heart of a PIM. Each attribute has a type, belongs to an attribute panel and is bound to an entity, and you create them from the browser rather than from code.

From here the usual next steps are to remove the starter catalogue, define the attributes your own articles need, group them into panels, and then load real data through the Import module or the REST API.
Server components
| Component | Version | Notes |
|---|---|---|
| AtroPIM | 1.16.5 | The Product Information Management application |
| AtroCore | 2.3.10 | The underlying data platform |
| Import / Import HTTP | 1.11.4 / 1.7.3 | Free import modules, files and HTTP sources |
| Export / Export HTTP | 1.11.5 / 1.9.3 | Free export modules, files and HTTP targets |
| PHP | 8.4 | FPM and CLI, from the ondrej PHP packages |
| PostgreSQL | 16 | Bound to the loopback interface only |
| nginx | 1.24 | Serves port 80 and proxies to PHP FPM |
| Ubuntu | 24.04 LTS | Fully patched, unattended security upgrades enabled |
Filesystem layout
| Path | Purpose |
|---|---|
/var/www/atropim |
Application root, holds composer.json, composer.lock and vendor/ |
/var/www/atropim/public |
The nginx document root, the only part of the tree served over HTTP |
/var/www/atropim/data |
Configuration, cache and generated metadata |
/var/www/atropim/upload |
Uploaded product images, datasheets and other assets |
/var/lib/postgresql/16/main |
The PostgreSQL cluster |
/root/atropim-credentials.txt |
Per VM credentials, mode 600, owned by root |
/var/lib/cloudimg |
First boot sentinel and the bootstrap ready marker |
Managing the services
systemctl status php8.4-fpm.service --no-pager | head -5
Restart the application after a configuration change:
sudo systemctl restart php8.4-fpm.service nginx.service
The job manager is driven by a timer that runs upstream's own cron entry every 60 seconds:
systemctl list-timers atropim-cron.timer --no-pager | head -3
Clear the AtroCore cache after changing the data model from the command line:
cd /var/www/atropim && sudo runuser -u www-data -- php console.php clear cache
Maintenance
Add your own domain name and a trusted TLS certificate before putting this VM in front of real users. The nginx site lives at /etc/nginx/sites-available/atropim and serves plain HTTP on port 80 only; adding a certificate is the single most important change to make.
Back up both halves of the system together: the PostgreSQL database holds your catalogue, and /var/www/atropim/upload holds the files attached to it.
sudo -u postgres pg_dump atropim | gzip > /var/backups/atropim-$(date +%F).sql.gz
sudo tar czf /var/backups/atropim-upload-$(date +%F).tar.gz -C /var/www/atropim upload
AtroCore updates itself through its own Module Manager, which uses the composer.phar at the application root. That file sits above the document root and is not reachable over HTTP.
Security recommendations
- Restrict
22/tcpto your management network and put a certificate in front of port 80 - Change the administrator password after your first sign in, and create named user accounts rather than sharing the administrator
- Keep PostgreSQL on the loopback interface; nothing outside the VM needs to reach it
- Take the starter catalogue out once you have loaded your own data
- Leave unattended security upgrades enabled so the base operating system keeps patching itself
Support
cloudimg provides 24/7 support for this image. For questions about AtroPIM itself, the upstream Help Center and community are the best starting points.