# Deploying the Web Portal With Docker

**URL:** <https://speciforum.org/t/deploying-the-web-portal-with-docker/2967>\
**Category:** Configuration & Installation\
**Tags:** Members-only\
**Created:** [October 2, 2025, 5:12pm UTC](https://speciforum.org/t/deploying-the-web-portal-with-docker/2967 "2025-10-02T17:12:57Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Grant](https://yyz1.discourse-cdn.com/flex029/user_avatar/speciforum.org/grant/32/4255_2.png) [@Grant](https://speciforum.org/u/Grant)\
**Post date:** [October 2, 2025, 5:12pm UTC](https://speciforum.org/t/deploying-the-web-portal-with-docker/2967/1 "2025-10-02T17:12:57Z")

</div>

# Web Portal with Docker

On Specify Cloud, we manage our Web Portal instances using a Docker image on EC2. This guide walks through deploying the Web Portal with Docker using the published image [`specifyconsortium/webportal-service:improve-build`](https://hub.docker.com/r/specifyconsortium/webportal-service).

## Deployment

First, clone the [webportal-installer repository](https://github.com/specify/webportal-installer) into a safe working directory. This directory will hold the portal files, configuration, and exports used by the container.

```bash
mkdir -p ~/webportal
cd ~/webportal
git clone https://github.com/specify/webportal-installer.git
mkdir -p specify_exports custom_settings custom-images

```

Create the `docker-compose.yml` file in `~/webportal`, alongside the `webportal-installer` directory.

### What it does

- Runs the Web Portal in a Docker container named `webportal`
- Serves web traffic on HTTP port 80
- Supports multiple portal instances for different organizations or museums
- Loads exported Specify data into Solr when the container starts

### Key features

- Uses the published Web Portal image, so no local Docker build is needed
- Supports portal-specific configurations and Specify database exports
- Supports custom branding images
- Restarts the container automatically if it stops
- Can be served over HTTPS through a separately configured TLS reverse proxy

**HTTPS note:** The Nginx configuration included in this image listens on port 80. Publishing port 443 or mounting Let’s Encrypt certificates into this container does not, by itself, enable HTTPS. Configure HTTPS on a reverse proxy in front of the portal if the portal will be publicly accessible.

### Volume mounts

The container connects these directories from the host:

- **Project files:** `./webportal-installer` contains the Web Portal application and build scripts.
- **Client exports:** `./specify_exports` contains the ZIP exports for each portal instance.
- **Custom settings:** `./custom_settings` contains each portal’s field model and settings.
- **Custom images:** `./custom-images` contains branding images served by the portal.

The paths beginning with `./` are relative to the directory containing `docker-compose.yml`. Store and edit the exports, settings, and images on the host rather than editing them inside the container.

```yaml
services:
  webportal:
    image: specifyconsortium/webportal-service:improve-build
    container_name: webportal
    ports:
      - "80:80"
    volumes:
      # Project files
      - ./webportal-installer:/home/specify/webportal-installer

      # Specify exports and customizations
      - ./specify_exports:/home/specify/webportal-installer/specify_exports
      - ./custom_settings:/home/specify/webportal-installer/custom_settings

      # Custom images
      - ./custom-images:/home/specify/custom-images

    restart: unless-stopped

```

The `specifyconsortium/` part of the image name is required. Without it, Docker searches for a different image named `webportal-service` and reports a pull access error.

The server structure should look something like this:

```plaintext
webportal/
├── custom-images/
├── custom_settings/
│ ├── portal1/
│ │ ├── fldmodel.json
│ │ └── settings.json
│ └── portal2/
│ ├── fldmodel.json
│ └── settings.json
├── docker-compose.yml
├── specify_exports/
│ ├── portal1.zip
│ └── portal2.zip
└── webportal-installer/
    ├── Dockerfile
    ├── Makefile
    ├── PortalApp/
    ├── README.md
    ├── webportal-nginx.conf
    └── ...

```

Start the service from the directory containing `docker-compose.yml`:

```bash
cd ~/webportal
docker compose up -d

```

To watch startup and data loading:

```bash
docker compose logs -f webportal

```

## Adding an Instance

Assuming the export made from Specify 6 is named `DataExport.zip`, use `scp` or `sftp` to copy the export and its configuration files to the Web Portal server.

The example below assumes:

- `webportal` is the server’s SSH host name.
- `/home/ubuntu/webportal` is the directory containing `docker-compose.yml`.
- The new portal instance will be named `ku_fish`.

Adjust these names and paths for your installation.

```bash
ssh webportal 'mkdir -p /home/ubuntu/webportal/custom_settings/ku_fish'
scp ./DataExport.zip webportal:/home/ubuntu/webportal/specify_exports/ku_fish.zip

unzip DataExport.zip
cd PortalFiles
scp flds.json webportal:/home/ubuntu/webportal/custom_settings/ku_fish/fldmodel.json
scp PortalInstanceSetting.json webportal:/home/ubuntu/webportal/custom_settings/ku_fish/settings.json

```

Repeat these steps for additional instances, using a distinct export filename and settings directory for each one.

## Loading New Data

When you add or replace an export or change custom settings, restart the Web Portal from the directory containing `docker-compose.yml`:

```bash
cd ~/webportal
docker compose restart webportal

```

The container’s startup command rebuilds the portal and runs `make load-data`. This can take several minutes. Follow the logs and verify the portal in a browser once startup finishes:

```bash
docker compose logs -f webportal

```

If you need to retry the data import without restarting the container, run:

```bash
docker compose exec webportal make load-data

```
