Applications AWS

Atomic CRM 1.6.1 on AWS User Guide

| Product: Atomic CRM 1.6.1 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Atomic CRM 1.6.1 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg ERP and CRM listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Atomic CRM is an open source customer relationship manager from marmelab, built with react-admin on Supabase. It keeps your contacts, companies, deals, notes and tasks in one place, with a deals pipeline as a kanban board, a dashboard of hot contacts, upcoming deal revenue and tasks, CSV import and export, and a team of sales people with an administrator.

What the cloudimg image adds:

  • A self hosted Supabase backend on the instance. PostgreSQL, the GoTrue authentication service, PostgREST, the Storage API with imgproxy, and the Kong API gateway run under Docker Compose, from images pinned by digest. Your CRM data never leaves your AWS account.
  • No default login, no demo keys. The PostgreSQL password, the JWT signing secret and the API keys signed with it, the Kong dashboard password, the Storage S3 key pair, the administrator password and the TLS key are all generated on your instance's first boot. None of Supabase's well known demo keys or passwords is accepted, and the image ships an empty data volume: the database is created on your instance.
  • Sign up closed. Public sign up is off in GoTrue and in Atomic CRM itself, and the sign up, invite, recovery and admin API routes are refused at nginx. New users are added on the instance by root with atomic-crm-add-user.
  • HTTPS from the first boot. nginx serves the web app and the three API paths it needs (/rest/v1/, /auth/v1/, /storage/v1/) on port 443 with a certificate generated on your instance; port 80 redirects to HTTPS and requests for a host name the instance does not serve are dropped. PostgreSQL and the gateway have no network listener.
  • Fail closed. nginx and the stack start only after first boot has finished; if it cannot finish, nothing is served.
  • Hardened defaults. No container can reach the instance metadata service (a firewall rule for every container, plus IPAddressDeny on nginx and Docker), the AMI requires IMDSv2, only root and nginx can reach the gateway on the loopback port, the API is handed the configured host name and the real client address from the connection (never values a client sends), and cross origin requests that change data are refused.
  • A reviewed build. The web app is built on the image from the Atomic CRM v1.6.1 release with npm ci --ignore-scripts on a reviewed, exact pinned lock file, inside a throwaway container; the image has no Node.js or compiler.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: Atomic CRM 1.6.1 (marmelab/atomic-crm v1.6.1, commit 4816c144), supabase/postgres 17.6.1.178, GoTrue v2.197.0, PostgREST v14.18, Storage API v1.89.12, imgproxy v4.0.21 and Kong 3.9.4 (each pulled by digest), Docker Engine 29.9.3 with the Compose plugin, nginx 1.24 from Ubuntu 24.04, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

The Atomic CRM deals pipeline as a kanban board, with deals in the Opportunity, Proposal Sent, In Negotiation, Won, Lost and Delayed columns and a total per column

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH) from your admin network, and TCP 443 (Atomic CRM) and TCP 80 (redirects to HTTPS) from the networks your team works from. PostgreSQL and the API gateway have no network port to open.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Atomic CRM 1.6.1 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.

The AMI has two volumes: a 20 GiB root volume for the operating system and the container images, and a 30 GiB data volume (/dev/sdf, mounted at /var/lib/atomic-crm) for the database and uploaded files. You can make the data volume larger at launch.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --block-device-mappings 'DeviceName=/dev/sdf,Ebs={VolumeSize=50,VolumeType=gp3}' \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=atomic-crm}]'

First boot takes two to four minutes once the instance is running: it creates the database, applies Atomic CRM's migrations and creates the administrator.

Step 2: Connect to your instance

Version SSH user
Atomic CRM 1.6.1 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the secrets and the certificate, creates the database and the administrator, then starts the stack and nginx:

systemctl is-active docker cloudimg-atomic-crm-netguard atomic-crm nginx atomic-crm-firstboot
sudo test -f /var/lib/cloudimg/atomic-crm-firstboot.done && echo FIRSTBOOT_DONE

All five units are active and the first boot marker is present:

active
active
active
active
active
FIRSTBOOT_DONE

The six containers are healthy:

sudo docker ps --format '{{.Names}}  {{.Status}}' | sed -E 's/Up [^(]+/Up /' | sort
atomic-crm-auth-1  Up (healthy)
atomic-crm-db-1  Up (healthy)
atomic-crm-imgproxy-1  Up (healthy)
atomic-crm-kong-1  Up (healthy)
atomic-crm-rest-1  Up (healthy)
atomic-crm-storage-1  Up (healthy)

Only SSH and nginx listen on network ports. The API gateway listens on the loopback address only, and PostgreSQL has no port on the host at all:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v ':53 ' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:8000 users:(("docker-proxy"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))

Step 4: Get your URL and password

sudo cat /root/atomic-crm-credentials.txt

The file is readable by root only:

# Atomic CRM 1.6.1 on Ubuntu 24.04 (cloudimg). Generated ON THIS INSTANCE at first boot; unique to it.
ATOMIC_CRM_URL=https://<public-dns-name>/
ATOMIC_CRM_TLS_SHA256=<certificate-fingerprint>
ATOMIC_CRM_ADMIN_EMAIL=admin@atomic-crm.local
ATOMIC_CRM_ADMIN_PASSWORD=<ATOMIC_CRM_ADMIN_PASSWORD>
# Sign in at ATOMIC_CRM_URL with the e-mail and password above (the certificate is self-signed: compare its
# fingerprint with ATOMIC_CRM_TLS_SHA256, or install your own certificate - see the guide).
# Sign-up is closed. Add a user:         sudo atomic-crm-add-user someone@example.com First Last [--admin]
# Reset a password (no mail server):     sudo atomic-crm-set-password someone@example.com
# Serve a DNS name instead of the IP:     sudo atomic-crm-set-hostname crm.example.com
# The Supabase API keys and the database password are in /etc/atomic-crm/secrets (root only); PostgreSQL and the API
# gateway listen inside the instance only.

The URL uses the instance's public DNS name from the EC2 instance metadata (its public IP address when the subnet gives no DNS name). The certificate also names the public and private IP addresses, and those addresses redirect to the URL, because the web app and the API are tied to one configured address. If you stop and start the instance without an Elastic IP, its public name changes and the instance picks up the new name on the next boot; to keep a fixed name, use an Elastic IP or your own DNS name (Step 8).

Step 5: Sign in

Browse to the URL from the credentials file. Your browser warns once about the self signed certificate: check that its SHA-256 fingerprint matches ATOMIC_CRM_TLS_SHA256, then accept it (or install your own certificate, Step 8). Sign in with the administrator e-mail and password from the credentials file.

The Atomic CRM sign in page with the Email and Password fields and the Sign in button

Forgot password? on the sign in page sends an e-mail, which needs a mail server the image does not configure, and the recovery route is refused; reset passwords on the instance instead (Step 7).

Step 6: Contacts, companies and deals

After signing in you land on the Dashboard: your hot contacts, the upcoming deal revenue by month, the latest activity and your upcoming tasks. On a new instance it starts as a short "What's next?" checklist with an Import data button.

The Atomic CRM dashboard with Hot Contacts, the Upcoming Deal Revenue chart, Latest Activity and Upcoming Tasks

Companies shows your organisations as cards with their contacts and deals; filter by size, sector and account manager, and use New Company or Import CSV to add them.

The Atomic CRM companies page with company cards, the deal count on each, and the size and sector filters

Contacts links each person to a company. A contact's page keeps the notes and tasks for that person, their status (cold, warm, hot, in contract), tags and e-mail addresses; notes can carry attachments, which are stored on the instance's data volume.

An Atomic CRM contact page with a note, the contact's status, personal and background information and a task

Deals is the pipeline as a kanban board, one column per stage (Opportunity, Proposal Sent, In Negotiation, Won, Lost, Delayed) with a total per column; drag a card to move a deal along. Every list can be exported to CSV.

Step 7: Users and passwords

Sign up is closed, so you add each colleague on the instance. The helper creates the account with a random password and writes it to /root/atomic-crm-user-<email>.txt (root only); the password is never printed. Add --admin to make the user an Atomic CRM administrator:

sudo atomic-crm-add-user <your-email> Sam Lee
sudo grep '^ATOMIC_CRM_USER_PASSWORD=' /root/atomic-crm-user-<your-email>.txt | cut -d= -f2-

No e-mail is sent: hand the address and password to the user yourself.

To reset a password on the instance, for a user or for the administrator, run the helper below. It sets a new random password and writes it to /root/atomic-crm-user-<email>.txt; for the administrator it also updates /root/atomic-crm-credentials.txt:

sudo atomic-crm-set-password <your-email>

To stop someone signing in, ban their account and mark them disabled in Atomic CRM (set banned_until back to null to let them in again). A browser session they already have stays valid until its token expires, within an hour:

printf "update auth.users set banned_until = '2999-12-31' where email = '%s';\nupdate public.sales set disabled = true where email = '%s';\n" <your-email> <your-email> \
  | sudo docker exec -i atomic-crm-db-1 psql -w -X -q -U postgres -d postgres

Features that need Supabase Edge Functions

This image runs Atomic CRM on Supabase's database, authentication, data API and storage services; it does not include the Supabase Edge Functions runtime. Four Atomic CRM features call an edge function and so do not work here: the Users page (creating, editing and disabling users in the browser; use the helpers and the command above), Change password on your profile (use atomic-crm-set-password), Merge with another contact, and the MCP Server URL shown on the profile page. Everything else, including contacts, companies, deals, notes, tasks, attachments, CSV import and export and the dashboard, works on the instance.

Step 8: Your own DNS name and certificate

Point a DNS record at the instance (an Elastic IP keeps the address stable), then set the name Atomic CRM is served as. This mints a new self signed certificate for the name, updates nginx, the API's configured address and the web app, recreates the containers whose settings changed, updates the URL and fingerprint in /root/atomic-crm-credentials.txt, and keeps the name across reboots:

sudo atomic-crm-set-hostname <your-domain>

To use a certificate from a public or internal certificate authority instead (for example one issued by Let's Encrypt for <your-domain>), set the name first, then copy your certificate and key over the generated pair and reload nginx (running atomic-crm-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/ssl/atomic-crm/atomic-crm.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/atomic-crm/atomic-crm.key
sudo nginx -t && sudo systemctl reload nginx

Step 9: Security checks

The shipped self test signs in as the administrator through GoTrue with this instance's password and reads the administrator's record through PostgREST, checks that an anonymous read returns nothing, that sign up and the GoTrue admin API are refused through nginx, and that no container can reach the instance metadata service. Passwords and keys are handed to curl in root only temporary files, never on a command line:

sudo atomic-crm-selftest
SELFTEST-OK (https://<public-dns-name>/ Atomic CRM 1.6.1: administrator sign-in + PostgREST read, anonymous read empty, sign-up and admin API refused, metadata blocked in 6 containers)

nginx serves the web app and the health check, refuses the sign up, admin, invite, recovery and Storage S3 routes, leaves the data API and the auth settings to the gateway (which wants an API key), redirects plain HTTP to HTTPS and gives no answer for a host name the instance does not serve:

H=$(sudo grep '^ATOMIC_CRM_URL=' /root/atomic-crm-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/?#\1#')
for p in / /healthz /auth/v1/signup /auth/v1/admin/users /auth/v1/invite /auth/v1/recover /storage/v1/s3 /rest/v1/ /auth/v1/settings; do
  curl -sk -o /dev/null -w "%{http_code}  $p\n" --resolve "$H:443:127.0.0.1" "https://$H$p"
done
curl -s -o /dev/null -w 'http:// -> %{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'unknown host name -> %{http_code}\n' --resolve "other.example:443:127.0.0.1" https://other.example/ || true
200  /
200  /healthz
403  /auth/v1/signup
403  /auth/v1/admin/users
403  /auth/v1/invite
403  /auth/v1/recover
403  /storage/v1/s3
401  /rest/v1/
401  /auth/v1/settings
http:// -> 301
unknown host name -> 000

The certificate was generated on this instance, names its own addresses and is not a certificate authority:

H=$(sudo grep '^ATOMIC_CRM_URL=' /root/atomic-crm-credentials.txt | cut -d= -f2- | sed -E 's#https://([^/]+)/?#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName,basicConstraints
subject=CN = <public-dns-name>, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:<public-dns-name>, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
X509v3 Basic Constraints: critical
    CA:FALSE

No container can reach the instance metadata service (000 is no answer), and the firewall rules behind that, together with the loopback and container network guards, are all in place:

for s in db auth rest storage imgproxy kong; do
  pid=$(sudo docker inspect -f '{{.State.Pid}}' $(sudo docker ps -qf name=-$s-))
  printf '%s %s\n' $s "$(sudo nsenter -t $pid -n curl -s -m 4 -o /dev/null -w '%{http_code}' http://169.254.169.254/latest/meta-data/ || true)"
done
sudo cloudimg-atomic-crm-netguard --check && echo GUARDS_OK
db 000
auth 000
rest 000
storage 000
imgproxy 000
kong 000
GUARDS_OK

nginx hands the gateway the configured host name and the address of the connection, and blanks the forwarded headers a client could send:

grep -E '^proxy_set_header (Host|X-Real-IP|X-Forwarded-For|X-Forwarded-Host|Forwarded) ' /etc/nginx/atomic-crm-proxy.conf
proxy_set_header Host $acrm_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host "";
proxy_set_header Forwarded "";

Step 10: Versions and where things are

Every container runs the image named in the shipped lock, by digest:

cat /opt/atomic-crm/images.lock
grep -E '^(Built|Atomic CRM):' /usr/share/doc/cloudimg-atomic-crm/BUILD-INFO.txt
cat /usr/share/doc/cloudimg-atomic-crm/npm-pins.txt
db supabase/postgres@sha256:c282d393ae56fd165b7ada2ddbac55e582a601b86f3840aa2bbc112e43d3a570
auth supabase/gotrue@sha256:1736a63078f5922b198c4cbe50f80ab9a2d3b54fe8b7b6cfb2e9dc5dbbc12c6b
rest postgrest/postgrest@sha256:c7cd7e265a85b05248abf53f4473fcec7e327c282d0bd0cf60f1551996d09fcf
storage supabase/storage-api@sha256:8526956cb462e9f7d392e9848ec77133b2769982ed614e9753cc687e8e4339a6
imgproxy darthsim/imgproxy@sha256:69ae09dbc3349944564cb2f66cf35f80a4a9a78fda73d06061a337849520ab1f
kong kong/kong@sha256:da2afeecfec0561dd2e06612b09c6283d79245a0387acdc3715271dbf5691a05
Built:              2026-11-07T12:41:28Z
Atomic CRM:         marmelab/atomic-crm v1.6.1 = 4816c1445d6fb7ccf767052337460bdf48bc0099 (2026-11-02), LICENSE.md sha256 1774c14a7b97aa0ce5e866f895ddb421db6dfb4769dee9ab40416681d0390316
pin dompurify 3.4.17
pin nanoid 3.3.20
pin postcss 8.5.32
pin qs 6.17.1
pin react-router 7.18.5
pin react-router-dom 7.18.5
pin source-map-js 1.2.2
pin vite 7.3.7
pin vite/node_modules/esbuild 0.28.2
pin zod 4.6.7
Component Path
Web app (static files nginx serves) /var/www/atomic-crm/app
Compose project, image lock, migrations /opt/atomic-crm
Stack settings and per instance secrets (root only) /etc/atomic-crm/stack.env, /etc/atomic-crm/secrets/
Database and file store (data volume) /var/lib/atomic-crm/db, /var/lib/atomic-crm/storage
nginx site and proxy settings /etc/nginx/sites-available/cloudimg-atomic-crm, /etc/nginx/atomic-crm-proxy.conf
Certificate and key /etc/ssl/atomic-crm/
Helpers /usr/local/sbin/atomic-crm-add-user, atomic-crm-set-password, atomic-crm-set-hostname, atomic-crm-selftest
Build record, licences and the npm lock file /usr/share/doc/cloudimg-atomic-crm/
Credentials /root/atomic-crm-credentials.txt (mode 0600)

The stack is one systemd unit: sudo systemctl restart atomic-crm restarts the containers, and sudo journalctl -u atomic-crm-firstboot shows the first boot log.

Step 11: Backup and maintenance

The database and the uploaded files live on the data volume at /var/lib/atomic-crm. The simplest backup is an EBS snapshot of that volume (stop the stack first, or snapshot both volumes together, for a consistent copy). For a database dump while Atomic CRM is running:

cd /opt/atomic-crm && sudo docker compose --env-file /etc/atomic-crm/stack.env -f compose.yaml exec -T db \
  pg_dump -U postgres -d postgres | gzip > <backup-dir>/atomic-crm-$(date +%F).sql.gz
sudo tar -czf <backup-dir>/atomic-crm-files-$(date +%F).tar.gz -C /var/lib/atomic-crm storage

The data volume can grow: enlarge it in EC2, then run sudo resize2fs on its device. Operating system, nginx and Docker security updates install automatically from Ubuntu and Docker's repository; run sudo apt-get update && sudo apt-get upgrade to apply everything else. The containers are pinned by digest and the web app is built into the image, so to move to a newer Atomic CRM release, launch the newer version from the listing and restore your dump into it, or contact cloudimg support. Take a snapshot first.

Step 12: Security recommendations

  • Allow port 22 from your admin network only, and ports 80 and 443 only from the networks your team works from.
  • Use your own DNS name and a certificate your browsers trust (Step 8).
  • Give each person their own account, and keep administrator rights for the people who manage users.
  • Change the administrator password if it may have been shared (Step 7), keep the operating system patched, and snapshot the data volume regularly.
  • Keep /etc/atomic-crm/secrets private: the service_key there bypasses every access rule in the database. It never leaves the instance and is only used on the loopback address.
  • The Atomic CRM 1.6.1 web app bundles decode-uri-component 0.2.2 (through react-admin's query-string), which has published denial of service advisories for malformed percent encoded input; no fixed version fits the ranges Atomic CRM 1.6.1's libraries accept. The code runs in each signed in user's own browser, where a crafted link can at worst make that browser tab slow; the server does not run it.

Support and licensing

Atomic CRM is open source software from marmelab, licensed under the MIT License; Supabase's components are licensed under the Apache License 2.0 (PostgREST under the MIT License, Kong under the Apache License 2.0, imgproxy under the MIT License). cloudimg ships Atomic CRM without source changes; the licences, the build record and the npm lock file are in /usr/share/doc/cloudimg-atomic-crm/. There is no per user licence fee. The AMI price covers cloudimg's hardening, first boot setup and support. Atomic CRM is a project of marmelab, and Supabase is a trademark of Supabase, Inc.; cloudimg is not affiliated with, endorsed by or sponsored by marmelab or Supabase.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA