Developer Tools AWS

Concord 2.46.0 on AWS User Guide

| Product: Concord 2.46.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and verifying Concord 2.46.0 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Node.js & Developer and API Tools AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other products in the listing are described on the listing's guide hub.

Concord is an open-source workflow and process-automation server from Walmart. You describe a process as a flow in a concord.yml file (steps, tasks, conditions, forms, retries), keep it in a Git repository or send it directly, and Concord runs it on an agent, keeping the log, the status, the variables and any attachments. Projects, repositories, secrets, JSON stores, triggers (cron, GitHub events) and teams are managed through the web console and the REST API.

The image runs Concord 2.46.0, the current release. 2.45.0 and 2.46.0 are security releases over 2.44.0: they fix authorisation checks, sanitise user-supplied Git and Docker parameters and an LDAP query parameter, and from 2.45.0 the agent WebSocket needs an explicit permission. The base is a fully patched Ubuntu 24.04 LTS, and unattended security upgrades stay on for your instance.

What is on the image:

  • concord-server 2.46.0 (REST API and the web console) running as the unprivileged concord user
  • concord-agent 2.46.0 as a separate unprivileged concord-agent user, which takes work from the server and starts every flow as a third account, concord-flow, that can read no configuration, key or credential
  • PostgreSQL 16 from Ubuntu, listening on loopback only, with its data on a separate 30 GiB EBS data volume mounted at /var/lib/concord
  • nginx publishing Concord over HTTPS on port 443 with a certificate created on your instance's first boot; port 80 redirects to HTTPS
  • OpenJDK 21 (headless) from Ubuntu
  • concord-set-hostname, a helper to serve Concord under your own DNS name, cloudimg-concord-probe, a sign-in and flow round-trip test, and concord-jsonstore-queries, the switch for JSON-store queries (off by default)
  • The build record (BUILD-INFO.txt), the jar list and the Apache-2.0 licence in /usr/share/doc/cloudimg-concord/

Who this image is for: one organisation whose flow authors are trusted administrators. A Concord flow can run any code by design (like a CI job), so anyone who can write or start flows is trusted with this instance. The image does not isolate one team's flows from another's, and it is not meant to be shared between organisations that do not trust each other.

Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3 · Data volume: 30 GiB gp3 at /var/lib/concord

Security posture

  • No default credentials. A stock Concord install is reached with an admin API key written into its configuration, and the documentation's example key is widely published. This image ships no database, no API key and no Concord configuration at all. On your instance's first boot, concord-firstboot creates the PostgreSQL cluster, the database roles and passwords, the secret-store keys and a random admin API key, and only then lets Concord start. The key is in /root/concord-credentials.txt (mode 0600, root only); Concord stores only its SHA-256 hash. Local users have no passwords and no LDAP or single sign-on realm is configured, so the API key is the only way in.
  • The admin key is never logged. Concord's own database migration prints the admin key it creates to standard output, which would put it in the system journal. On this image that generation is turned off and first boot stores the key's hash directly, so the key never appears in any log.
  • Every API route needs the key. Without a valid key every /api/v1 and /api/v2 route answers 401, except the health check (/api/v1/server/ping) and the version (/api/v1/server/version). Concord's Prometheus endpoint /metrics is not served off the instance.
  • The agent has the least it needs. The agent signs in as its own user, agent, whose only role (agentWebsocket) lets it take work over the WebSocket; its key cannot reach any admin route. The WebSocket permission check that 2.45.0 introduced stays on.
  • Flows cannot reach your secrets, the agent's key or the database. Every flow runs as its own account, concord-flow, not as the agent. It cannot read the agent's configuration (which holds the agent's API key) or the agent's process, the server's configuration (database passwords, secret-store keys), /root/concord-credentials.txt, the TLS key or PostgreSQL's files, and it runs with no Linux capabilities and cannot gain privileges through set-user-ID programs. nftables refuses its connections to PostgreSQL, to the agent's maintenance port and to the metadata service; a flow talks to the Concord API only with its own process session token.
  • The agent cannot do more than start flows. The agent itself holds no Linux capabilities and runs one flow at a time. The only thing it may run with sudo is a small root helper that starts a flow as its own short-lived systemd service (as concord-flow, with no metadata access and a read-only system apart from the flow's working directory). When a flow finishes or is cancelled, every process it started is stopped, including background processes it detached, and links or special files it left in its working directory are removed before the agent collects the flow's attachments.
  • JSON-store queries are disabled by default (not safe for multi-organisation use). JSON stores work as usual: create stores and read and write their items in the console, the API and your flows. Running SQL queries against a store is switched off, because Concord's query feature is not safe when several organisations share one server. If every Concord user on your instance may see every organisation's JSON-store data, an administrator can switch queries on (Step 8).
  • Least-privilege database roles. Concord connects as concord and concord_inventory, plain login roles without superuser, create-database or create-role rights; PostgreSQL accepts them over loopback only, with SCRAM-SHA-256.
  • Only nginx is reachable. Concord's own port (8001, which has no bind-address setting) and the agent's maintenance port are dropped for any traffic not on loopback, and PostgreSQL listens on loopback only.
  • HTTPS on first boot. nginx serves Concord with a TLS key and self-signed certificate created on your instance (TLS 1.2 and 1.3 only). Port 80 only redirects to HTTPS. The session cookie is Secure and HttpOnly.
  • Fixed host name. Concord is served under a name taken from the instance metadata at first boot (or one you set), never from the request; requests for any other host name are dropped. Concord answers cross-origin (CORS) requests only for that name.
  • Cross-site requests refused. nginx refuses cross-site form posts and WebSocket upgrades, and also cross-site GET requests to the two routes that act on a GET: starting a process from a repository link (/api/v1/org/<org>/project/<project>/repo/<repo>/start/<entry-point>) and restoring a process checkpoint (/api/v2/process/<id>/checkpoint). The web console works as usual. A script that calls one of those two GET routes must send the header Sec-Fetch-Site: same-origin; everything else in the API is unaffected.
  • Real client addresses. nginx passes the connecting address to Concord and ignores any X-Forwarded-For header a client sends.
  • No metadata access. Concord fetches Git repositories and templates from URLs you give it and runs your flows, so concord-server, concord-agent, nginx and PostgreSQL are all blocked from reaching the instance metadata service (169.254.169.254 and fd00:ec2::254). Amazon Time Sync keeps working.
  • Hardened services. Concord's services run with no Linux capabilities and a read-only system (ProtectSystem=strict) except their own data directories; the application files are owned by root, and no service writes core dumps or heap dumps.
  • No scheduled firmware-metadata refresh. The fwupd firmware updater stays installed, but its LVFS metadata refresh (fwupd-refresh.timer and .service) is masked, so an LVFS outage never marks your instance unhealthy at boot; run sudo fwupdmgr refresh yourself if you use it.
  • IMDSv2 required. The AMI is registered with ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests.
  • Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.

What anyone who can reach the instance can read

Without a key, Concord answers only its health check (/healthz and /api/v1/server/ping), its version (/api/v1/server/version) and the static files of the web console's sign-in page. Everything else (processes, logs, projects, secrets, JSON stores, users and keys) needs an API key.

Known advisories and how the build handles them

A scan of the Concord 2.46.0 distribution found one critical and fourteen high advisories, all in third-party libraries with fixes in the same release line. The build replaces them with the fixed versions, checked by SHA-256 against Maven Central:

  • Bouncy Castle 1.84 to 1.85 (bcprov, bcpkix, bcutil), in the server and the agent
  • Jackson (jackson-core, jackson-databind) 2.22.1 to 2.22.3, in the server, the agent and inside the agent's two flow runners
  • Gson 2.8.8 to 2.8.9, in the server

The patched set scans clean of high and critical advisories. The exact jar list ships in /usr/share/doc/cloudimg-concord/jars.txt, and the flows in this guide run on the patched runners.

Prerequisites

  1. An AWS account and permission to launch EC2 instances
  2. An EC2 key pair in the Region you launch in
  3. A subnet that gives the instance a public IPv4 address (or private connectivity from where you will use Concord)

Recommended instance type: m5.large (2 vCPU, 8 GiB). Concord runs two Java services and PostgreSQL; for many parallel flows choose a larger type.

Connecting to your instance

Version Operating system SSH user
Concord 2.46.0 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. Open the cloudimg Hardened Node.js & Developer and API Tools AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Concord 2.46.0 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended; the software charge is $0.04 per vCPU per hour) and your key pair.
  5. Security group: allow SSH (22), HTTP (80) and HTTPS (443). Restrict SSH to your own IP address and 80/443 to the people and API clients that use Concord.
  6. Choose your VPC and a subnet that gives the instance a public IP, keep the second (data) volume the image defines, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH and HTTPS from your management address only, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR with your own:

SG=$(aws ec2 create-security-group --group-name concord \
  --description "Concord" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
for p in 22 80 443; do
  aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port "$p" --cidr <your-mgmt-cidr>
done
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=concord}]'

You do not need --metadata-options: the AMI already makes IMDSv2 required. The data volume is part of the AMI; to start with a bigger one, add --block-device-mappings '[{"DeviceName":"/dev/sdf","Ebs":{"VolumeSize":100,"VolumeType":"gp3"}}]' and grow the filesystem as described in Maintenance.

Step 2: Connect and read your credentials

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot usually finishes within a minute or two of launch: it creates the certificate, the secrets, the PostgreSQL cluster and the admin key, starts Concord (whose first start creates its database schema), and then gives the agent its own key. Your credentials are in a root-only file:

sudo cat /root/concord-credentials.txt
# Concord - generated on this instance's first boot by concord-firstboot.service.
# Unique to this instance; nothing here shipped in the image. Store it somewhere safe.
CONCORD_URL=https://ec2-44-203-91-20.compute-1.amazonaws.com/
CONCORD_CONSOLE_LOGIN_URL=https://ec2-44-203-91-20.compute-1.amazonaws.com/#/login?useApiKey=true
CONCORD_ADMIN_USER=admin
CONCORD_ADMIN_TOKEN=<your-api-key>
#
# Open CONCORD_CONSOLE_LOGIN_URL and sign in with the API key above (self-signed certificate minted on this instance;
# port 80 redirects). REST API clients send it as the Authorization header: Authorization: <CONCORD_ADMIN_TOKEN>.
# The agent has its own key (user 'agent', role agentWebsocket only). Create named API keys for people and
# automation, and delete this one once you have your own admin key.
# Change the name Concord is served as:  sudo concord-set-hostname <your-dns-name>
# Restrict 80/443 in the security group to the people and clients that need them.
# Guide: https://www.cloudimg.co.uk/guides/concord-2-46-0-on-ubuntu-24-04-aws/

Your file shows your instance's own name and a 32-character key in place of <your-api-key>.

Step 3: Confirm the services are running

systemctl is-active concord-port-guard postgresql@16-main concord-server concord-agent nginx
curl -s --cacert /etc/ssl/cloudimg/concord.crt https://localhost/healthz; echo
curl -s --cacert /etc/ssl/cloudimg/concord.crt https://localhost/api/v1/server/version; echo
curl -s -o /dev/null -w 'without a key: %{http_code}\n' --cacert /etc/ssl/cloudimg/concord.crt https://localhost/api/v1/org
curl -s --cacert /etc/ssl/cloudimg/concord.crt https://localhost/api/v1/server/version | grep -q '"version":"2.46.0"'
active
active
active
active
active
{"ok":true}
{"version":"2.46.0","commitId":"e5d500485bc83bd0fee123b6534837bb43b4f97c","env":"n/a","ok":true}
without a key: 401

Only SSH, HTTP and HTTPS are reachable from outside; Concord's port 8001 is dropped for anything but loopback, and PostgreSQL and the agent's maintenance port listen on loopback only:

sudo ss -Hltn | awk '{print $4}' | sort
sudo nft list chain inet cloudimg_concord input | grep -q 'iifname != "lo" tcp dport { 5432, 8001, 8010 }'
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
0.0.0.0:8001
127.0.0.1:5432
127.0.0.1:8010
127.0.0.53%lo:53
127.0.0.54:53
[::]:22
[::]:443
[::]:80

Step 4: Sign in to the web console

  1. Open CONCORD_CONSOLE_LOGIN_URL from the credentials file in your browser. The certificate is self-signed for your instance, so your browser asks you to accept it the first time (or use your own certificate, Step 9).
  2. The sign-in page opens in API key mode. Paste the value of CONCORD_ADMIN_TOKEN and choose Login.
  3. You are signed in as admin. The console shows your processes, the Default organisation with its projects, secrets and teams, and your profile, where you can create more API keys.

The Concord console sign-in page in API key mode, with the API Key field and the Login button

The Concord console Activity page after signing in as admin, listing the last processes with their status, project, repository and initiator

From the command line, the same key authenticates the REST API in the Authorization header:

H="Authorization: $(sudo sed -n 's/^CONCORD_ADMIN_TOKEN=//p' /root/concord-credentials.txt)"
curl -s --cacert /etc/ssl/cloudimg/concord.crt -H "$H" https://localhost/api/service/console/whoami; echo
curl -s --cacert /etc/ssl/cloudimg/concord.crt -H "$H" https://localhost/api/service/console/whoami | grep -q '"username":"admin"'
{"realm":"apikey","username":"admin","displayName":"admin","orgs":[{"id":"0fac1b18-d179-11e7-b3e7-d7df4543ed4f","name":"Default"}]}

Step 5: Run your first flow

Write a flow and send it to Concord. The agent picks it up, runs it and Concord keeps the log:

H="Authorization: $(sudo sed -n 's/^CONCORD_ADMIN_TOKEN=//p' /root/concord-credentials.txt)"
D=$(mktemp -d) && cd "$D"
printf 'configuration:\n  runtime: concord-v2\nflows:\n  default:\n    - log: "Hello from Concord"\n    - log: "Running as ${initiator.username} in process ${txId}"\n' > concord.yml
ID=$(curl -s --cacert /etc/ssl/cloudimg/concord.crt -H "$H" -F concord.yml=@concord.yml https://localhost/api/v1/process | python3 -c 'import json,sys; print(json.load(sys.stdin)["instanceId"])')
for i in $(seq 1 60); do S=$(curl -s --cacert /etc/ssl/cloudimg/concord.crt -H "$H" https://localhost/api/v1/process/$ID | python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])'); case "$S" in FINISHED|FAILED|CANCELLED) break ;; esac; sleep 2; done
echo "status=$S"
curl -s --cacert /etc/ssl/cloudimg/concord.crt -H "$H" https://localhost/api/v1/process/$ID/log | grep -E 'Hello|Running as|finished'
cd / && rm -rf "$D"
[ "$S" = FINISHED ]
status=FINISHED
2026-10-27T15:53:09.017+0000 [INFO ] Hello from Concord
2026-10-27T15:53:09.137+0000 [INFO ] Running as admin in process c3ed2e7a-3ca3-47c2-8059-ec6933741c46
2026-10-27T15:53:11.028+0000 [INFO ] Process finished with: 0

The Concord console page for a finished process, with the FINISHED badge and the Logs tab showing the system log and the two log task entries

The first flow takes a few seconds longer because the agent downloads Concord's standard task plugins from Maven Central and caches them. The process also appears in the console under Processes, with its log, its status history and its variables.

The flow ran as concord-flow, the account that runs every flow. sudo cloudimg-concord-probe runs the same check end to end (key accepted, no key and a wrong key refused, agent connected, a test flow finished) and prints CONCORD_PROBE_OK.

Step 6: Run a flow from a Git repository

Most teams keep their flows in Git. Create a project, register a repository and start its flow. This example uses the "hello world" example from Concord's own repository:

H="Authorization: $(sudo sed -n 's/^CONCORD_ADMIN_TOKEN=//p' /root/concord-credentials.txt)"
C=(-s --cacert /etc/ssl/cloudimg/concord.crt -H "$H")
curl "${C[@]}" -H 'Content-Type: application/json' -d '{"name":"examples","visibility":"PRIVATE"}' https://localhost/api/v1/org/Default/project; echo
curl "${C[@]}" -H 'Content-Type: application/json' -d '{"name":"hello","url":"https://github.com/walmartlabs/concord.git","branch":"master","path":"examples/hello_world"}' https://localhost/api/v1/org/Default/project/examples/repository; echo
ID=$(curl "${C[@]}" -F org=Default -F project=examples -F repo=hello https://localhost/api/v1/process | python3 -c 'import json,sys; print(json.load(sys.stdin)["instanceId"])')
for i in $(seq 1 60); do S=$(curl "${C[@]}" https://localhost/api/v1/process/$ID | python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])'); case "$S" in FINISHED|FAILED|CANCELLED) break ;; esac; sleep 2; done
echo "status=$S"
curl "${C[@]}" https://localhost/api/v1/process/$ID/log | grep -E 'Hello|finished'
[ "$S" = FINISHED ]
{"id":"01a17e10-cfff-73fc-b28c-84a7db7a26e4","result":"CREATED","ok":true}
{"result":"CREATED","ok":true}
status=FINISHED
2026-10-27T15:53:34.731+0000 [INFO ] Hello, Concord!
2026-10-27T15:53:36.125+0000 [INFO ] Process finished with: 0

The Concord console page for the examples project in the Default organisation, listing two finished processes started from the hello repository

For a private repository, store its credentials as a Concord secret (console: Organizations, Default, Secrets) and pick it when you add the repository.

Concord can also start a repository's flow from a plain link, https://<your-host>/api/v1/org/<org>/project/<project>/repo/<repo>/start/<entry-point>, which the console offers as Start process. Because a link like that could be planted on another web site to start a process with your session, nginx accepts it only when the request comes from Concord's own pages. Scripts send Sec-Fetch-Site: same-origin (or use POST /api/v1/process as above):

H="Authorization: $(sudo sed -n 's/^CONCORD_ADMIN_TOKEN=//p' /root/concord-credentials.txt)"
U=https://localhost/api/v1/org/Default/project/examples/repo/hello/start/default
X=$(curl -s -o /dev/null -w '%{http_code}' --cacert /etc/ssl/cloudimg/concord.crt -H "$H" -H 'Sec-Fetch-Site: cross-site' "$U"); echo "cross-site link: $X"
O=$(curl -s -o /dev/null -w '%{http_code}' --cacert /etc/ssl/cloudimg/concord.crt -H "$H" -H 'Sec-Fetch-Site: same-origin' "$U"); echo "same-origin: $O"
[ "$X" = 403 ] && [ "$O" = 200 ]
cross-site link: 403
same-origin: 200

Step 7: Create your own API keys

Create a named key for each person and each automation instead of sharing the first-boot key. As admin:

H="Authorization: $(sudo sed -n 's/^CONCORD_ADMIN_TOKEN=//p' /root/concord-credentials.txt)"
curl -s --cacert /etc/ssl/cloudimg/concord.crt -H "$H" -H 'Content-Type: application/json' \
  -d '{"username":"admin","name":"my-laptop"}' https://localhost/api/v2/apikey
{"id":"01a17d38-1a27-70f0-bbec-21594449fb1d","name":"my-laptop","key":"<your-new-api-key>","result":"CREATED","ok":true}

The key is shown once; store it safely. Users sign in to the console with their own key the same way as in Step 4. To add a person, create a local user (POST /api/v1/user with {"username":"alice","type":"LOCAL"}), give them roles or team membership, and create a key for them; keys can also be created and deleted in the console under the user's API keys. Once you have your own admin key, delete the first-boot key named cloudimg-admin (DELETE /api/v2/apikey/<id>; list them with GET /api/v1/apikey) and remove it from /root/concord-credentials.txt.

Step 8: JSON stores and queries

JSON stores keep structured data that flows and API clients share. Create a store, write an item and read it back:

H="Authorization: $(sudo sed -n 's/^CONCORD_ADMIN_TOKEN=//p' /root/concord-credentials.txt)"
C=(-s --cacert /etc/ssl/cloudimg/concord.crt -H "$H" -H 'Content-Type: application/json')
curl "${C[@]}" -d '{"name":"inventory"}' https://localhost/api/v1/org/Default/jsonstore; echo
curl "${C[@]}" -X PUT -d '{"hosts":["web-1","web-2"]}' https://localhost/api/v1/org/Default/jsonstore/inventory/item/servers; echo
curl "${C[@]}" https://localhost/api/v1/org/Default/jsonstore/inventory/item/servers; echo
curl "${C[@]}" https://localhost/api/v1/org/Default/jsonstore/inventory/item/servers | grep -q '"web-2"'
{"result":"CREATED","ok":true}
{"result":"CREATED","ok":true}
{"hosts": ["web-1", "web-2"]}

JSON-store queries are disabled by default. Concord can also run SQL queries over a store's items, but that feature is not safe for multi-organisation use, so it is switched off on this image: a query is refused with permission denied, while stores and items keep working.

sudo concord-jsonstore-queries status
sudo concord-jsonstore-queries status | grep -qx 'JSON-store queries: disabled'
JSON-store queries: disabled

Switch queries on only if every Concord user on this instance may see every organisation's JSON-store data (for example, a single team using only the Default organisation). As root:

sudo concord-jsonstore-queries enable     # queries run; not safe for multi-organisation use
sudo concord-jsonstore-queries disable    # back to the default

Step 9: HTTPS certificate and host name

The certificate was created on your instance's first boot for the instance's public DNS name, public IP, private IP and localhost. The key, the configuration, the credentials file and the data directories are readable only by their owners:

sudo openssl x509 -in /etc/ssl/cloudimg/concord.crt -noout -subject -ext subjectAltName
sudo stat -c '%a %U:%G %n' /etc/ssl/cloudimg/concord.key /etc/concord/server.conf /etc/concord/agent.conf /root/concord-credentials.txt /var/lib/concord/postgresql /var/lib/concord/server /var/lib/concord/agent
subject=CN = ec2-44-203-91-20.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:ec2-44-203-91-20.compute-1.amazonaws.com, IP Address:44.203.91.20, IP Address:172.31.94.172, IP Address:127.0.0.1, DNS:localhost
600 root:root /etc/ssl/cloudimg/concord.key
640 root:concord /etc/concord/server.conf
640 root:concord-agent /etc/concord/agent.conf
600 root:root /root/concord-credentials.txt
700 postgres:postgres /var/lib/concord/postgresql
750 concord:concord /var/lib/concord/server
2750 concord-agent:concord-work /var/lib/concord/agent

To serve Concord under your own DNS name (for example concord.example.com, pointed at this instance), run the helper. It records the name, creates a new self-signed certificate for it, updates nginx, Concord's allowed origin and the cross-site guard, rewrites the URLs in the credentials file and restarts Concord:

sudo concord-set-hostname concord.example.com

If the instance's public address changes after a stop and start, run the helper again. To use a certificate from your own certificate authority (recommended before others sign in), replace /etc/ssl/cloudimg/concord.key (keep it 0600 root) and /etc/ssl/cloudimg/concord.crt, then run sudo systemctl reload nginx.

Step 10: Outbound connections and metadata

Concord clones the repositories you register and the agent downloads task plugins from Maven Central, so the instance needs outbound HTTPS. None of Concord's services, nginx or PostgreSQL can reach the instance metadata service, while the Amazon Time Sync Service at 169.254.169.123 keeps working, and the firmware-metadata refresh is masked:

systemctl show -p IPAddressDeny concord-server.service concord-agent.service nginx.service postgresql@16-main.service
chronyc -n sources | grep 169.254
FW=$(systemctl is-enabled fwupd-refresh.timer fwupd-refresh.service || true); echo "$FW"
[ "$(sort -u <<<"$FW")" = masked ] && [ "$(systemctl show -p IPAddressDeny --value concord-server.service concord-agent.service nginx.service postgresql@16-main.service | grep -c 169.254.169.254)" = 4 ]
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128

IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128

IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128

IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
^* 169.254.169.123               3   4   377    13    -23us[  +21us] +/-  433us
masked
masked

If your flows need AWS credentials, store them as Concord secrets (or use a dedicated IAM user's keys) rather than the instance role: flows cannot read the instance role from the metadata service.

Maintenance

  • Data volume: everything Concord keeps (the PostgreSQL database with processes, logs, projects, secrets and keys, the server's working files and the agent's cache) is on the EBS volume mounted at /var/lib/concord. To grow it, modify the volume in the EC2 console, then run sudo resize2fs $(findmnt -no SOURCE /var/lib/concord).
  • Backups: take EBS snapshots of the data volume, or a logical backup with sudo -u postgres pg_dump concord > concord.sql (Concord can keep running). Keep backups as safe as the credentials: the database holds your secrets (encrypted with keys in /etc/concord/server.conf, which you must back up too) and your API key hashes.
  • Agents: the image runs one agent that runs one flow at a time; further flows wait in the queue. Concord can use more agents on other instances; each needs its own user with the agentWebsocket role and its own key, and must reach this instance's /websocket over HTTPS.
  • Upgrades: to move to a newer Concord release, launch the newer version from the listing when it is published and restore your database backup.
  • Logs: sudo journalctl -u concord-server -n 100 --no-pager, sudo journalctl -u concord-agent -n 100 --no-pager, and nginx's access log in /var/log/nginx/access.log. Process logs are in the console and the API.
  • Security patches: unattended-upgrades remains enabled so the OS keeps receiving security updates.

Security recommendations

  • Keep the security group tight. Open 22 only to your management address and 80/443 only to the people and API clients that use Concord.
  • Flow authors are trusted administrators. A flow can run scripts and reach the network from the instance, and all flows run as the same concord-flow account. Give project write access and API keys only to people you trust to run code on this instance, and use one instance per organisation.
  • Keep JSON-store queries disabled unless every Concord user may see every organisation's JSON-store data.
  • Use your own certificate before other people sign in, so they trust it without exceptions.
  • Give each person and automation its own API key, delete the first-boot key once you have your own, and delete keys you no longer use.

Support and licensing

Concord is developed by Walmart and contributors and distributed under the Apache License 2.0. This image ships the official Concord 2.46.0 server and agent distributions from Maven Central, unmodified apart from the library updates listed above; the licence and the build record are in /usr/share/doc/cloudimg-concord/. The bundled libraries keep their own open-source licences. OpenJDK, PostgreSQL and nginx come from Ubuntu. cloudimg provides the packaging, the hardening, first-boot HTTPS and per-instance credentials, and 24/7 support.

cloudimg is not affiliated with or endorsed by Walmart or the Concord project. Product names are marks of their owners and are used here only to identify the software.

For help with deployment or configuration, email support@cloudimg.co.uk or visit cloudimg support.