Installation via Docker Compose

Running via Docker Compose is the recommended, fastest and most stable way to deploy LockMyCal on your own virtual or dedicated server (VPS).

Important

LockMyCal doesn't have a publicly published Docker image yet – you build the image yourself from the source code. Docker Compose does it automatically straight from the repository (github.com/lockmycal/lockmycal, the public repository is being prepared).


System requirements

  • OS: Linux (Ubuntu 22.04+, Debian 12+, Rocky Linux or macOS)
  • Docker: Engine 24.0+ and Docker Compose v2+
  • Hardware: At least 1 GB of RAM (2 GB recommended) and 1 vCPU
  • Domain / DNS: An A/AAAA record set up pointing to your public IP address (e.g. calendar.yourdomain.com)

1. Preparing the directory and the .env configuration

Create a directory for LockMyCal and prepare the .env configuration file:

mkdir -p /opt/lockmycal && cd /opt/lockmycal

Generate secure keys:

# Generating the key for web sessions (64 characters)
openssl rand -base64 64 | tr -d '\n'

# Generating the key for encrypting OAuth tokens to calendars (48 characters)
openssl rand -base64 48 | tr -d '\n'

Create the .env file:

# ==========================================
# REQUIRED SECURITY SETTINGS
# ==========================================
SECRET_KEY_BASE=insert_the_generated_64_character_key
DATA_ENCRYPTION_KEY=insert_the_generated_48_character_key
PHX_HOST=calendar.yourdomain.com

# ==========================================
# DATABASE SETTINGS (PostgreSQL)
# ==========================================
POSTGRES_USER=lockmycal
POSTGRES_PASSWORD=choose_a_strong_database_password   # required
POSTGRES_DB=lockmycal_prod
DATABASE_URL=postgres://lockmycal:choose_a_strong_database_password@db:5432/lockmycal_prod

# ==========================================
# APPLICATION SETTINGS
# ==========================================
APP_NAME="LockMyCal"
PORT=4000
LISTEN_IP=0.0.0.0
REGISTRATION_ENABLED=true

# ==========================================
# EMAIL NOTIFICATIONS (SMTP)
# ==========================================
EMAIL_ADAPTER=smtp
EMAIL_FROM_ADDRESS="bookings@yourdomain.com"
EMAIL_FROM_NAME="LockMyCal Bookings"
SMTP_HOST=smtp.yourdomain.com
SMTP_PORT=587
SMTP_USERNAME=bookings@yourdomain.com
SMTP_PASSWORD=smtp_password

2. Creating the docker-compose.yml file

In the same folder, create the docker-compose.yml file:

services:
  app:
    build:
      # Docker downloads the repository by itself; after the # is the branch or the version tag
      context: https://github.com/lockmycal/lockmycal.git#${LOCKMYCAL_VERSION:-main}
      dockerfile: Dockerfile.docker
    image: lockmycal:${LOCKMYCAL_VERSION:-main}
    container_name: lockmycal-app
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "127.0.0.1:4000:4000"
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - app_data:/app/data

  db:
    image: postgres:17-alpine
    container_name: lockmycal-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-lockmycal}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB:-lockmycal_prod}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-lockmycal} -d ${POSTGRES_DB:-lockmycal_prod}"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  pgdata:
  app_data:

3. Starting and initialization

Start the containers:

docker compose up -d --build

Note

The values in .env are read literally – neither ${VAR} nor $(command) is expanded. Variables can also be stored in the file /app/data/.env in the application's volume, which the application loads at startup (the database keys POSTGRES_USER, POSTGRES_DB, DATABASE_URL, DATABASE_HOST and DATABASE_PORT are not read from it).

Watch the startup and the running of the migrations:

docker compose logs -f app

Important

First Administrator registration: The first user who registers in the empty database through the web interface after startup automatically becomes the main administrator. As soon as at least one account is created, this automatic gate closes permanently.


4. Managing the administrator via CLI / RPC

If you need to promote an existing user to administrator later, run:

docker compose exec app bin/tymeslot rpc 'Tymeslot.Release.promote_admin("user@yourdomain.com")'

To remove administrator rights:

docker compose exec app bin/tymeslot rpc 'Tymeslot.Release.demote_admin("user@yourdomain.com")'

5. Configuring the reverse proxy (Nginx)

To ensure HTTPS and SSL certificates (e.g. via Let's Encrypt / Certbot), add this configuration to Nginx:

server {
    listen 80;
    server_name calendar.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name calendar.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/calendar.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/calendar.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:4000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
    }
}