Skip to content

Installation

Make sure you meet the requirements first.

1. Get the code

git clone https://github.com/LundDarkLab/adc.git
cd adc

2. Configure

Create your .env from the template and fill in your values:

cp .env.example .env
nano .env

The file is fully commented; in short you must set:

  • the database credentials (DB_USER, DB_PASSWORD, DB_NAME, DB_ROOT_PASSWORD) β€” the MySQL container is initialised with these values on its first start;
  • the SMTP parameters (MAIL*) used for registration and password-reset emails.

Values with special characters

Wrap passwords containing !, $, #, & or spaces in single quotes: DB_PASSWORD='my&secret!'.

3. Build and start

Two equivalent options:

A β€” Build from source (default):

docker compose up -d --build

B β€” Prebuilt image from the GitHub Container Registry, if you don't need to modify the code:

docker compose -f docker-compose.ghcr.yml up -d

This pulls ghcr.io/lunddarklab/adc instead of building locally β€” faster, and updates are a docker compose -f docker-compose.ghcr.yml pull && docker compose -f docker-compose.ghcr.yml up -d away. Everything else (configuration, database initialisation, storage) is identical.

On the first start the MySQL container imports every .sql / .sql.gz file found in db-init/, in alphabetical order:

File Content
00-schema.sql Database schema and views
01-seed-data.sql Base data: controlled vocabularies, time series, licenses
02-first-admin.sql The default administrator account

This happens only once, while the database volume is empty.

Check that everything is up:

docker compose ps          # web "running", db "running (healthy)"
docker compose logs -f db  # follow the first import

The application now answers on http://127.0.0.1:8081.

The find-site forms and the map rely on the administrative boundaries published by GADM (country β†’ province β†’ district β†’ municipality). They are not bundled with the platform β€” the GADM license allows free academic and non-commercial use but not redistribution β€” so each installation downloads them directly from the source, only for the countries it actually needs.

With the stack running, import one or more countries by their ISO 3166-1 alpha-3 code:

./scripts/import-gadm.sh SWE            # Sweden
./scripts/import-gadm.sh NOR DNK FIN    # add more countries at any time

For each country the script downloads the official GeoPackage from gadm.org and loads every administrative level into the gadm0…gadm5 tables with ogr2ogr, run from the official GDAL Docker image β€” no local GDAL installation required. Re-running the script with new codes appends to the existing data.

Note

The first run pulls the GDAL image (~1.5 GB, one-time). The platform works without the geographic dataset, but the find-site selectors and the geographic layers of the map stay empty until you import at least one country.

Manual import (what the script does under the hood)

Each administrative level N is a layer named ADM_ADM_N in the GADM GeoPackage, loaded into the corresponding gadmN table:

bash curl -fLO https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_SWE.gpkg ogr2ogr -f MySQL "MYSQL:dbname,host=127.0.0.1,port=3306,user=...,password=..." \ gadm41_SWE.gpkg ADM_ADM_1 \ -nln gadm1 -append -update -relaxedFieldNameMatch

The target tables already exist (created by 00-schema.sql) with SRID 4326 geometry columns, so any import method that respects their structure works.

5. First login

Log in with the default administrator account:

  • email: admin@example.com
  • password: changeme

Change the password immediately (Settings β†’ Manage your data profile), then update the account's name and email β€” or create your real administrator account and disable the default one.

Initial configuration

Most controlled vocabularies ship pre-filled (materials, conservation states, licenses, user roles…), but the ones that depend on the scientific scope of your institution ship empty on purpose and must be filled by an administrator before contributors start creating records:

What Where to fill it Needed for
Category class and Category specification Vocabularies page Classifying artefacts
At least one timeline, with its macro / generic / specific periods Timeline page The chronological definition of artefacts and the timeline browsing

Optionally, the cultural_generic_period table feeds the chronological-distribution chart; it has no editing interface yet and can be populated directly in the database if you want that chart.

Re-running the initialisation

The db-init/ files are applied only to an empty database volume. If the first import fails midway (or you want to start over), wipe the volume with docker compose down -v β€” this deletes all data β€” and run docker compose up -d again.

6. Reverse proxy and HTTPS

The web container is bound to the loopback interface on purpose: public traffic should arrive through a reverse proxy on the host, which owns the domain and terminates TLS.

Apache

<VirtualHost *:443>
    ServerName collections.example.org

    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/live/collections.example.org/fullchain.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/collections.example.org/privkey.pem

    ProxyPreserveHost On
    ProxyPass        / http://127.0.0.1:8081/
    ProxyPassReverse / http://127.0.0.1:8081/

    # Uploads of 3D models can be large
    LimitRequestBody 0
</VirtualHost>

Required modules: a2enmod proxy proxy_http ssl headers.

nginx

server {
    listen 443 ssl;
    server_name collections.example.org;

    ssl_certificate     /etc/letsencrypt/live/collections.example.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/collections.example.org/privkey.pem;

    client_max_body_size 0;   # uploads of 3D models can be large

    location / {
        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Obtain the certificates with certbot.

Local test without a proxy

For a quick test you can open http://localhost:8081 directly, or temporarily change the port mapping in docker-compose.yml from 127.0.0.1:8081:80 to 8081:80 to reach the instance from other machines on the network. Do not run a public production instance without TLS.

7. Storage directories

Uploaded content is stored outside the container: archive/ (models, images, documents β€” the repository already contains the required folder skeleton) and img/ (logos and icons), both mounted into the web container. By default they are the folders of the repository itself; to keep the data elsewhere on the server, set ARCHIVE_DIR and IMG_DIR in .env:

ARCHIVE_DIR=/srv/dyncoll/archive
IMG_DIR=/srv/dyncoll/img

The container entrypoint automatically aligns file-ownership UIDs between the host directories and the Apache user.

Development setup

For local development, create a docker-compose.override.yml (ignored by git) that mounts the source code into the container, so changes are visible without rebuilding:

services:
  web:
    volumes:
      - .:/var/www/html
      - ./archive:/var/www/html/archive
      - ./img:/var/www/html/img

Compose picks the override up automatically with docker compose up.

Next: Architecture Β· Maintenance