Deploy to Docker

This guide walks you through deploying Cube.js with Docker.

This is an example of a production-ready deployment, but real-world deployments can vary significantly depending on desired performance and scale.

Create a Docker Compose stack by creating a docker-compose.yml. A production-ready stack would at minimum consist of:

  • One or more Cube.js API instance
  • A Cube.js Refresh Worker
  • A Cube Store Router node
  • One or more Cube Store Worker nodes
  • A Redis instance

An example stack using BigQuery as a data source is provided below:

Using macOS or Windows? Use CUBEJS_DB_HOST=host.docker.internal instead of localhost if your database is on the same machine.

version: '2.2'

services:
  cube_api:
    restart: always
    image: cubejs/cube:v0.30.75
    ports:
      - 4000:4000
    environment:
      - CUBEJS_DB_TYPE=bigquery
      - CUBEJS_DB_BQ_PROJECT_ID=cubejs-bq-cluster
      - CUBEJS_DB_BQ_CREDENTIALS=<BQ-KEY>
      - CUBEJS_DB_EXPORT_BUCKET=cubestore
      - CUBEJS_CUBESTORE_HOST=cubestore_router
      - CUBEJS_REDIS_URL=redis://redis:6379
      - CUBEJS_API_SECRET=secret
    volumes:
      - .:/cube/conf
    depends_on:
      - cubestore_worker_1
      - cubestore_worker_2
      - cube_refresh_worker
      - redis

  cube_refresh_worker:
    restart: always
    image: cubejs/cube:v0.30.75
    environment:
      - CUBEJS_DB_TYPE=bigquery
      - CUBEJS_DB_BQ_PROJECT_ID=cubejs-bq-cluster
      - CUBEJS_DB_BQ_CREDENTIALS=<BQ-KEY>
      - CUBEJS_DB_EXPORT_BUCKET=cubestore
      - CUBEJS_CUBESTORE_HOST=cubestore_router
      - CUBEJS_REDIS_URL=redis://redis:6379
      - CUBEJS_API_SECRET=secret
      - CUBEJS_REFRESH_WORKER=true
    volumes:
      - .:/cube/conf

  cubestore_router:
    restart: always
    image: cubejs/cubestore:v0.30.75
    environment:
      - CUBESTORE_WORKERS=cubestore_worker_1:10001,cubestore_worker_2:10002
      - CUBESTORE_REMOTE_DIR=/cube/data
      - CUBESTORE_META_PORT=9999
      - CUBESTORE_SERVER_NAME=cubestore_router:9999
    volumes:
      - .cubestore:/cube/data

  cubestore_worker_1:
    restart: always
    image: cubejs/cubestore:v0.30.75
    environment:
      - CUBESTORE_WORKERS=cubestore_worker_1:10001,cubestore_worker_2:10002
      - CUBESTORE_SERVER_NAME=cubestore_worker_1:10001
      - CUBESTORE_WORKER_PORT=10001
      - CUBESTORE_REMOTE_DIR=/cube/data
      - CUBESTORE_META_ADDR=cubestore_router:9999
    volumes:
      - .cubestore:/cube/data
    depends_on:
      - cubestore_router

  cubestore_worker_2:
    restart: always
    image: cubejs/cubestore:v0.30.75
    environment:
      - CUBESTORE_WORKERS=cubestore_worker_1:10001,cubestore_worker_2:10002
      - CUBESTORE_SERVER_NAME=cubestore_worker_2:10002
      - CUBESTORE_WORKER_PORT=10002
      - CUBESTORE_REMOTE_DIR=/cube/data
      - CUBESTORE_META_ADDR=cubestore_router:9999
    volumes:
      - .cubestore:/cube/data
    depends_on:
      - cubestore_router

  redis:
    image: bitnami/redis:latest
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
    logging:
      driver: none

In production, the Cube.js API should be served over an HTTPS connection to ensure security of the data in-transit. We recommend using a reverse proxy; as an example, let's use NGINX.

You can also use a reverse proxy to enable HTTP 2.0 and GZIP compression

First we'll create a new server configuration file called nginx/cube.conf:

server {
  listen 443 ssl;
  server_name cube.my-domain.com;

  ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
  ssl_ecdh_curve              secp384r1;
  # Replace the ciphers with the appropriate values
  ssl_ciphers                 "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384 OLD_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 OLD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
  ssl_prefer_server_ciphers   on;
  ssl_certificate             /etc/ssl/private/cert.pem;
  ssl_certificate_key         /etc/ssl/private/key.pem;
  ssl_session_timeout         10m;
  ssl_session_cache           shared:SSL:10m;
  ssl_session_tickets         off;
  ssl_stapling                on;
  ssl_stapling_verify         on;

  location / {
    proxy_pass http://cube:4000/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

Then we'll add a new service to our Docker Compose stack:

services:
  ...
  nginx:
    image: nginx
    ports:
      - 443:443
    volumes:
      - ./nginx:/etc/nginx/conf.d
      - ./ssl:/etc/ssl/private

Don't forget to create a ssl directory with the cert.pem and key.pem files inside so the Nginx service can find them.

For automatically provisioning SSL certificates with LetsEncrypt, this blog post may be useful.

Cube.js can be configured to use industry-standard JSON Web Key Sets for securing its API and limiting access to data. To do this, we'll define the relevant options on our Cube.js API instance:

If you're using queryRewrite for access control, then you must also configure scheduledRefreshContexts so the refresh workers can correctly create pre-aggregations.

services:
  cube_api:
    image: cubejs/cube:v0.30.75
    ports:
      - 4000:4000
    environment:
      - CUBEJS_DB_TYPE=bigquery
      - CUBEJS_DB_BQ_PROJECT_ID=cubejs-bq-cluster
      - CUBEJS_DB_BQ_CREDENTIALS=<BQ-KEY>
      - CUBEJS_DB_EXPORT_BUCKET=cubestore
      - CUBEJS_CUBESTORE_HOST=cubestore_router
      - CUBEJS_REDIS_URL=redis://redis:6379
      - CUBEJS_API_SECRET=secret
      - CUBEJS_JWK_URL=https://cognito-idp.<AWS_REGION>.amazonaws.com/<USER_POOL_ID>/.well-known/jwks.json
      - CUBEJS_JWT_AUDIENCE=<APPLICATION_URL>
      - CUBEJS_JWT_ISSUER=https://cognito-idp.<AWS_REGION>.amazonaws.com/<USER_POOL_ID>
      - CUBEJS_JWT_ALGS=RS256
      - CUBEJS_JWT_CLAIMS_NAMESPACE=<CLAIMS_NAMESPACE>
    volumes:
      - .:/cube/conf
    depends_on:
      - cubestore_worker_1
      - cubestore_worker_2
      - cube_refresh_worker
      - redis

All Cube Store nodes (both router and workers) should only be accessible to Cube.js API instances and refresh workers. To do this with Docker Compose, we simply need to make sure that none of the Cube Store services have any exposed ports.

All Cube.js logs can be found by through the Docker Compose CLI:

$ docker-compose ps

           Name                           Command               State                    Ports
---------------------------------------------------------------------------------------------------------------------------------
cluster_cube_1                 docker-entrypoint.sh cubej ...   Up      0.0.0.0:4000->4000/tcp,:::4000->4000/tcp
cluster_cubestore_router_1     ./cubestored                     Up      3030/tcp, 3306/tcp
cluster_cubestore_worker_1_1   ./cubestored                     Up      3306/tcp, 9001/tcp
cluster_cubestore_worker_2_1   ./cubestored                     Up      3306/tcp, 9001/tcp

$ docker-compose logs

cubestore_router_1    | 2021-06-02 15:03:20,915 INFO  [cubestore::metastore] Creating metastore from scratch in /cube/.cubestore/data/metastore
cubestore_router_1    | 2021-06-02 15:03:20,950 INFO  [cubestore::cluster] Meta store port open on 0.0.0.0:9999
cubestore_router_1    | 2021-06-02 15:03:20,951 INFO  [cubestore::mysql] MySQL port open on 0.0.0.0:3306
cubestore_router_1    | 2021-06-02 15:03:20,952 INFO  [cubestore::http] Http Server is listening on 0.0.0.0:3030
cube_1                | 🚀 Cube.js server (0.30.75) is listening on 4000
cubestore_worker_2_1  | 2021-06-02 15:03:24,945 INFO  [cubestore::cluster] Worker port open on 0.0.0.0:9001
cubestore_worker_1_1  | 2021-06-02 15:03:24,830 INFO  [cubestore::cluster] Worker port open on 0.0.0.0:9001

Find the latest stable release version (currently v0.30.75) from Docker Hub. Then update your docker-compose.yml to use the tag:

version: '2.2'

services:
  cube_api:
    image: cubejs/cube:v0.30.75
    ports:
      - 4000:4000
    environment:
      - CUBEJS_DB_TYPE=bigquery
      - CUBEJS_DB_BQ_PROJECT_ID=cubejs-bq-cluster
      - CUBEJS_DB_BQ_CREDENTIALS=<BQ-KEY>
      - CUBEJS_DB_EXPORT_BUCKET=cubestore
      - CUBEJS_CUBESTORE_HOST=cubestore_router
      - CUBEJS_REDIS_URL=redis://redis:6379
      - CUBEJS_API_SECRET=secret
    volumes:
      - .:/cube/conf
    depends_on:
      - cubestore_router
      - cube_refresh_worker
      - redis

If you need to use npm packages with native extensions inside the cube.js configuration file, you'll need to build your own Docker image. You can do this by first creating a Dockerfile and a corresponding .dockerignore:

touch Dockerfile
touch .dockerignore

Add this to the Dockerfile:

FROM cubejs/cube:latest

COPY . .
RUN npm install

And this to the .dockerignore:

node_modules
npm-debug.log
schema
cube.js
.env

Then start the build process by running the following command:

docker build -t <YOUR-USERNAME>/cubejs-custom-build .

Finally, update your docker-compose.yml to use your newly-built image:

version: '2.2'

services:
  cube_api:
    image: <YOUR-USERNAME>/cubejs-custom-build
    ports:
      - 4000:4000
    environment:
      - CUBEJS_DB_TYPE=bigquery
      - CUBEJS_DB_BQ_PROJECT_ID=cubejs-bq-cluster
      - CUBEJS_DB_BQ_CREDENTIALS=<BQ-KEY>
      - CUBEJS_DB_EXPORT_BUCKET=cubestore
      - CUBEJS_CUBESTORE_HOST=cubestore_router
      - CUBEJS_REDIS_URL=redis://redis:6379
      - CUBEJS_API_SECRET=secret
    volumes:
      - .:/cube/conf
      # Prevent dev dependencies leaking
      - .empty:/cube/conf/node_modules/@cubejs-backend/
    depends_on:
      - cubestore_router
      - cube_refresh_worker
      - redis

Did you find this page useful?