Self-host Contember
This is an advanced guide. You don't need this - skip the hassle and deploy to Contember Cloud. To complete this guide, you are expected to know how to run Docker containers in production.
What you'll need
- PostgreSQL database (e.g. AWS RDS for PostgreSQL, DigitalOcean Managed Database)
- S3-compatible storage for file uploads (optional if you don't need file upload capability; e.g. AWS S3, DigitalOcean Spaces, MinIO, CEPH, Zenko CloudServer)
- SMTP server to send emails from (optional)
- Some way to run Docker image.
Deploy Contember Engine
1. Setup PostgresSQL database
Required PostgresSQL version is 10 or newer.
Contember needs the following databases:
- tenant (for information about users and their roles) and
- content database for each deployed project.
Contember itself manages these databases and runs migrations upon startup. You don't need to create content database if the PostgresSQL user given to Contember has permission to create it on its own.
2. Setup S3 bucket
This bucket will be used to store user uploads. You will need a key and secret with permissions to create objects.
3. Setup SMTP server
Get SMTP credentials for your mailing server - used for example for password reset.
4. Run Contember Engine Docker container
Contember Engine is deployed as a Docker container. It's published on Docker Hub as contember/engine
.
Configure the following environment variables:
NODE_ENV: "production"
CONTEMBER_PORT: "4000" # Port on which the service will be available
CONTEMBER_LOGIN_TOKEN: "..." # Login token used (20 random [0-9a-f] characters; can be generated by `openssl rand -hex 20` command)
CONTEMBER_ROOT_TOKEN: "..." # Root token used (20 random [0-9a-f] characters; can be generated by `openssl rand -hex 20` command)
CONTEMBER_ROOT_EMAIL: "[email protected]" # Superadmin user's e-mail
CONTEMBER_ROOT_PASSWORD: "my-secret-password" # Superadmin user's password
# Credentials for database:
DEFAULT_DB_HOST: "your-db-server.com"
DEFAULT_DB_PORT: "5432"
DEFAULT_DB_USER: "postgres"
DEFAULT_DB_PASSWORD: "postgres"
DEFAULT_DB_SSL: "true"
TENANT_DB_NAME: "tenant" # Name of tenant database - see first step
DEFAULT_DB_NAME: "my-project" # Optional - name of database to
# or BLOG_DB_NAME: "blog" where blog is slug of the project.
# S3 credentials - see step 2
DEFAULT_S3_REGION: "rgn1"
DEFAULT_S3_KEY: "..."
DEFAULT_S3_SECRET: "..."
DEFAULT_S3_ENDPOINT: "https://rgn1.yours3provider.com"
DEFAULT_S3_BUCKET: "your-bucket"
# SMTP credentials - see step 3
TENANT_MAILER_HOST: "send.your-smtp.com"
TENANT_MAILER_PORT: "2525"
TENANT_MAILER_FROM: "[email protected]"
HTTP server should start on the specified port. You can add a load-balancer in front of the app. The container is stateless and therefore horizontally scalable.
After the server is running Contember CLI can be used to set up other API tokens and invite users. Deploy your projects to this instance by running contember deploy blog --yes
command with CONTEMBER_API_URL
and CONTEMBER_API_TOKEN
environment variables set. You can use either superadmin token or create token with deployer
role (using contember tenant:create-api-key
command).
Deploy Contember Interface
Contember Interface can be deployed as single page application. It's bundled using Vite.
You will need to modify admin/index.tsx
and admin/vite.config.ts
files based on following examples:
import * as React from 'react'
import { ApplicationEntrypoint, LoginEntrypoint, Pages, runReactApp } from '@contember/admin'
import { Layout } from './components/Layout'
import '@contember/admin/index.css'
const apiBaseUrl = import.meta.env.VITE_CONTEMBER_ADMIN_API_BASE_URL as string
const loginToken = import.meta.env.VITE_CONTEMBER_ADMIN_LOGIN_TOKEN as string
const projectSlug = import.meta.env.VITE_CONTEMBER_ADMIN_PROJECT_NAME as string
if (window.location.pathname === '/') {
// Login page
runReactApp(
<LoginEntrypoint
apiBaseUrl={apiBaseUrl}
loginToken={loginToken}
projects={[projectSlug]}
formatProjectUrl={it => `/${it.slug}/`}
/>,
)
} else if (window.location.pathname.startsWith('/' + projectSlug)) {
// Project interface itself page
runReactApp(
<ApplicationEntrypoint
basePath={`/${projectSlug}/`}
apiBaseUrl={apiBaseUrl}
project={projectSlug}
stage="live"
children={<Pages layout={Layout} children={import.meta.glob('./pages/**/*.tsx')} />}
/>
)
} else {
// Page not found - redirect to login
window.location.href = '/'
}
import { defineConfig } from 'vite'
export default defineConfig(({ command }) => ({
base: '/',
}))
Furthermore your .env
files (.env.development
and .env.production
) should contain referenced variables:
# URL where Engine is running
VITE_CONTEMBER_ADMIN_API_BASE_URL=http://localhost:1481
# Login token needed to perform login - configured in env variable of Engine
VITE_CONTEMBER_ADMIN_LOGIN_TOKEN=1111111111111111111111111111111111111111
# Your project's slug (used to connect to correct GraphQL endpoint)
VITE_CONTEMBER_ADMIN_PROJECT_NAME=headless-cms
After these changes you can run npm run build-admin
command. It will create bundle in admin/dist
folder, which you can deploy to any hosting, which needs to serve the index.html
file for any request that doesn't match assets.