Deployment guide

Atomic CRM 1.6.1

Overview

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. It adds a kanban deals pipeline, a dashboard of hot contacts, upcoming deal revenue and tasks, CSV import and export, and a sales team with an administrator.

This SecureAnyCloud image runs Atomic CRM 1.6.1 on Ubuntu 24.04 LTS on Amazon EC2. It is one version of the SecureAnyCloud ERP and CRM listing on AWS Marketplace: you pick it as the software version when you subscribe and launch. The image runs its own Supabase backend on the instance, so your CRM data never leaves your AWS account.

What you get:

  • 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.
  • No default login and no demo keys. The instance generates 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 on its first boot. The image rejects all of Supabase's well-known demo keys and passwords. It ships an empty data volume, and the database is created on your instance.
  • Sign-up closed. Public sign-up is off in GoTrue and in Atomic CRM itself. nginx refuses the sign-up, invite, recovery and admin API routes. Root adds new users on the instance 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 first boot cannot finish, nothing is served.
  • Hardened defaults. No container can reach the instance metadata service: a firewall rule covers every container, and IPAddressDeny applies to nginx and Docker. The AMI requires IMDSv2. Only root and nginx can reach the gateway on the loopback port. The API receives the configured host name and the real client address from the connection, never values a client sends. 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, using a reviewed, exactly pinned lock file inside a throwaway container. The image has no Node.js or compiler.
  • A fully patched base. Ubuntu 24.04 LTS is patched at build time, with unattended security upgrades switched on.
  • 24/7 SecureAnyCloud support

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. It also includes 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

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. It must also allow 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.
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, select your Region, and choose Launch through EC2. Choose m5.large, your key pair and a security group as described above.

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

To launch with the AWS CLI instead, 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.

2

Connect to your instance

Connect over SSH as the default user. For Atomic CRM 1.6.1 on Ubuntu 24.04, the SSH user is ubuntu.

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

On first boot, the instance generates the secrets and the certificate, creates the database and the administrator, and then starts the stack and nginx. Check the first-boot result:

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

Verify that all five units are active and that the first-boot marker is present:

active active active active active FIRSTBOOT_DONE

Confirm that 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. Confirm the listeners:

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"))
4

Get your URL and password

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

Only root can read this file:

# Atomic CRM 1.6.1 on Ubuntu 24.04 (sac). 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, or 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).

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 email and password from the credentials file.

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

6

Contacts, companies and deals

After you sign in, you land on the Dashboard. It shows 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.

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 companies.

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

Deals is the pipeline as a kanban board, with one column per stage (Opportunity, Proposal Sent, In Negotiation, Won, Lost, Delayed) and a total per column. Drag a card to move a deal along. You can export every list to CSV.

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-

The helper sends no email, so give the address and password to the user yourself.

To reset a password 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, so they do not work here:

  • The Users page, which creates, edits and disables users in the browser. Use the helpers and the command above instead.
  • Change password on your profile. Use atomic-crm-set-password instead.
  • Merge with another contact
  • The MCP Server URL shown on the profile page

Everything else works on the instance, including contacts, companies, deals, notes, tasks, attachments, CSV import and export, and the dashboard.

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 that Atomic CRM is served as.

This command creates a new self-signed certificate for the name, updates nginx, the API's configured address and the web app, and recreates the containers whose settings changed. It also 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 your certificate 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
9

Security checks

The shipped self-test does the following:

  • 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
  • Checks that sign-up and the GoTrue admin API are refused through nginx
  • Checks 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. Run the self-test:

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. It refuses the sign-up, admin, invite, recovery and Storage S3 routes, and leaves the data API and the auth settings to the gateway, which requires an API key. It 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 instance generated the certificate itself. It names the instance's 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 = sac 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 means no answer). 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 sac-atomic-crm-netguard --check && echo GUARDS_OK
db 000 auth 000 rest 000 storage 000 imgproxy 000 kong 000 GUARDS_OK

nginx passes the gateway the configured host name and the address of the connection, and blanks any forwarded headers a client sends:

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 "";
10

Versions and where things are

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

cat /opt/atomic-crm/images.lock grep -E '^(Built|Atomic CRM):' /usr/share/doc/sac-atomic-crm/BUILD-INFO.txt
cat /usr/share/doc/sac-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

Key files and directories (component, then 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/sac-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/sac-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.

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. For a consistent copy, stop the stack first, or snapshot both volumes together.

To take 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

To grow the data volume, enlarge it in EC2, then run sudo resize2fs on its device.

Security updates for the operating system, nginx and Docker 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. To move to a newer Atomic CRM release, launch the newer version from the listing and restore your dump into it, or contact SecureAnyCloud support. Take a snapshot first.

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 used only on the loopback address.
  • Be aware of one known dependency advisory. 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 that 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 and imgproxy are under the MIT License, and Kong is under the Apache License 2.0. SecureAnyCloud ships Atomic CRM without source changes. The licences, the build record and the npm lock file are in /usr/share/doc/sac-atomic-crm/.

There is no per-user licence fee. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.

Atomic CRM is a project of marmelab, and Supabase is a trademark of Supabase, Inc. SecureAnyCloud is not affiliated with, endorsed by or sponsored by marmelab or Supabase.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24-hour response SLA

Deploy on AWS

Launch Atomic CRM 1.6.1 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS