Deployment guide

FoodCoopShop

Overview

FoodCoopShop is user-friendly open source software for food cooperatives, buying groups and small local producers. Members browse a shared catalogue of products from many producers, order for a weekly delivery or pickup rhythm, and manage their account and credit balance. Administrators manage producers, products, categories, orders, invoicing and pickup days from a dedicated admin area.

FoodCoopShop supports many delivery rhythms, a self-service stock mode with optional barcode scanning, and order adaptions such as cancellation and weight or price changes. It also includes a cashless payment system based on account credit.

This SecureAnyCloud image delivers FoodCoopShop fully installed and configured on Ubuntu 24.04. Database migrations and base data are already applied and the shop is set to English, so you land directly on a working shop and admin area. There is no setup wizard to complete.

What you get:

  • FoodCoopShop 4.2.0 (AGPL-3.0), a CakePHP 5 application served from /var/www/foodcoopshop/webroot
  • nginx, PHP 8.4 (php8.4-fpm with OPcache) and MySQL 8.0
  • A per-VM CakePHP Security.salt and cookieKey, a per-VM MySQL password and a per-VM administrator email and password, all generated at first boot and written to a root-only file
  • MySQL bound to 127.0.0.1 only. nginx on 80/tcp is the only public surface.
  • An example catalogue, a demo producer with three products, so the admin area and storefront show a working shop from the first boot
  • nginx.service, php8.4-fpm.service and mysql.service as enabled, active systemd units. foodcoopshop-firstboot.service rotates the secrets before nginx serves the first request.
  • 24/7 SecureAnyCloud support

Prerequisites

You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.

Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting size. Choose a larger size for bigger cooperatives or heavier catalogues.

Configure the network security group (NSG) to allow inbound 22/tcp from your management network. Allow 80/tcp from your members, plus 443/tcp once you enable HTTPS.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for FoodCoopShop by SecureAnyCloud, and select Create.

On the Basics tab, 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 select Review + create and Create.

Step 2 - Deploy from the Azure CLI

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:foodcoopshop:default:latest
az vm create \
  --resource-group <your-rg> \
  --name foodcoopshop \
  --image sac1647283583153:foodcoopshop:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name foodcoopshop --port 80 --priority 1010

Step 3 - Connect to your VM

Connect over SSH:

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

Check that the three services behind FoodCoopShop all report active, that the shop front answers with HTTP 200, and that MySQL listens on loopback only:

systemctl is-active nginx php8.4-fpm mysql curl -s -o /dev/null -w 'shop front / -> HTTP %{http_code}\n' http://127.0.0.1/ ss -ltn | grep -E ':80 |:3306'

Expected output: three lines of active, then HTTP 200, and a :3306 socket bound to 127.0.0.1 only (never 0.0.0.0).

Step 5 - Retrieve your administrator credentials

On the first boot of every VM, a one-shot service (foodcoopshop-firstboot.service) generates values that are unique to that VM. These are a fresh CakePHP application Security.salt and cookieKey, a fresh MySQL password, and a fresh administrator email and password. The service also sets the VM's own address as the application base URL.

No shared or default credentials ship in the image, and nginx does not serve a single request until this rotation is complete. Read the credentials with sudo:

sudo cat /root/foodcoopshop-credentials.txt

The file (mode 0600, root only) contains the administrator email (foodcoopshop.admin.user), the administrator password (foodcoopshop.admin.pass), the shop URL and the database credentials. FoodCoopShop signs in by email.

Step 6 - Sign in to the admin area

Browse to http://<vm-public-ip>/sign-in and sign in with the administrator email and password from the credentials file. You land on the admin panel, where you manage producers, products, orders, members and the website.

You can also verify the login round trip from the VM's own shell. FoodCoopShop protects the login form with both a CSRF token and CakePHP FormProtection, so a real sign-in replays all of them. A successful login answers HTTP 302, and the authenticated GET /admin then answers HTTP 200:

U=$(sudo grep '^foodcoopshop.admin.user=' /root/foodcoopshop-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^foodcoopshop.admin.pass=' /root/foodcoopshop-credentials.txt | cut -d= -f2-)
CJ=$(mktemp);
PG=$(mktemp)
curl -s -c "$CJ" http://127.0.0.1/sign-in -o "$PG"
CSRF=$(grep -oE 'name="_csrfToken" value="[^"]*"' "$PG" | head -1 | sed -E 's/.*value="([^"]*)".*/\1/')
TF=$(grep -oE 'name="_Token\[fields\]" value="[^"]*"' "$PG" | head -1 | sed -E 's/.*value="([^"]*)".*/\1/')
TU=$(grep -oE 'name="_Token\[unlocked\]" value="[^"]*"' "$PG" | head -1 | sed -E 's/.*value="([^"]*)".*/\1/')
curl -s -o /dev/null -b "$CJ" -c "$CJ" -w 'sign-in POST -> HTTP %{http_code}\n' \
  --data-urlencode "_csrfToken=$CSRF" --data-urlencode "_Token[fields]=$TF" \
  --data-urlencode "_Token[unlocked]=$TU" --data-urlencode "email=$U" \
  --data-urlencode "passwd=$P" --data-urlencode "remember_me=0" http://127.0.0.1/sign-in
curl -s -o /dev/null -b "$CJ" -w 'GET /admin (authed) -> HTTP %{http_code}\n' http://127.0.0.1/admin rm -f "$CJ" "$PG"

Step 7 - Manage producers and the catalogue

In the admin panel's left navigation, open Manufacturers to manage your producers. The image ships a demo producer, SecureAnyCloud Demo Producer, with three example products, so you can see the catalogue working immediately.

Add your own producers. Then use Products to add and price their products, Members to manage member accounts, and Orders to view and adapt orders.

The member-facing storefront at http://<vm-public-ip>/ presents the shop with search, the producer list, the shopping cart and each member's credit balance. Configure your delivery and pickup rhythm under Website administration so that members can order for the next delivery day.

Step 8 - Confirm the version and the seeded catalogue

Check the installed version and the example catalogue:

cat /var/www/foodcoopshop/VERSION.txt
sudo mysql foodcoopshop -e "SELECT (SELECT count(*) FROM fcs_manufacturer WHERE active=1) AS producers, (SELECT count(*) FROM fcs_product WHERE active=1) AS active_products;"

The version file reports 4.2.0. The seeded example catalogue shows one active producer with three active products.

Administration from the CLI

FoodCoopShop is a CakePHP application. Admin CLI tasks run through bin/cake as the www-data user. For example, list the available commands:

sudo -u www-data php8.4 /var/www/foodcoopshop/bin/cake.php

The application configuration lives in /var/www/foodcoopshop/config/custom_config.php. It holds the database settings, Security.salt, cookieKey and App.fullBaseUrl. FoodCoopShop's queue runs scheduled tasks such as order reminders and invoicing. See the FoodCoopShop cronjobs documentation.

Enabling HTTPS

For production, point a real domain at the VM's public IP and open 443/tcp in the NSG. Then terminate TLS at nginx with Let's Encrypt, replacing the example domain with yours:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.com

After the certificate is issued, set your new address as the shop base URL in config/custom_config.php. Use the App.fullBaseUrl value with no trailing slash, for example https://your-domain.example.com. Then reload nginx and php-fpm.

Backup and maintenance

Back up the MySQL database. The database password is in the credentials file:

sudo mysqldump foodcoopshop > foodcoopshop-backup.sql

Snapshot the VM's OS disk in Azure for a point-in-time backup of the application and database together.

Keep the OS patched with sudo apt update && sudo apt upgrade. Unattended security upgrades are enabled by default. To restart the whole stack cleanly, run sudo systemctl restart nginx php8.4-fpm mysql.

Support

This image is backed by 24/7 SecureAnyCloud support. Contact us by email and chat for help with FoodCoopShop deployment, upgrades, delivery-rhythm and pickup-day configuration, email and SMTP setup, invoicing and payments, and database administration.

All product and company names are trademarks or registered trademarks of their respective holders. Their use does not imply any affiliation with or endorsement by them.

Deploy on Azure

Launch FoodCoopShop on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure