Files
twenty/packages/twenty-docker/docker-compose.yml
Fabian 6f62636763 Refactor: Improve Docker volume permission handling and remove run-once service (#11405)
**Problem:**

The previous `docker-compose.yml` included a `change-vol-ownership`
service. This service was designed to run once upon startup to `chown`
the `server-local-data` and `docker-data` volumes to user/group
`1000:1000`. This was necessary because:
1. The main `server` and `worker` containers run as the non-root user
`1000` for security.
2. Docker typically creates/mounts named volumes initially owned by
`root`.
3. The application needs write access to these volumes.

However, this run-once service pattern causes problems in certain
deployment environments (like Coolify) that don't gracefully handle
services designed to exit after completing their task. This can lead to
deployment failures or warnings.

**Solution:**

This PR refactors the Docker setup to address the volume permission
issue directly within the Docker image build process, eliminating the
need for the run-once service.

**Changes:**

1.  **`packages/twenty-docker/docker-compose.yml`:**
    *   Removed the `change-vol-ownership` service definition entirely.
* Removed the `depends_on: change-vol-ownership` condition from the
`server` service definition.
* **Proposed Change:** Removed the `${STORAGE_LOCAL_PATH}` environment
variable from the `server-local-data` volume mounts for both `server`
and `worker` services. The path is now hardcoded to
`/app/packages/twenty-server/.local-storage`. (See Reasoning below).

2.  **`packages/twenty-docker/twenty/Dockerfile`:**
* In the final stage, *before* the `USER 1000` command, added lines to:
* Create the necessary directories: `RUN mkdir -p
/app/packages/twenty-server/.local-storage /app/docker-data` (and also
`/app/.local-storage` for safety, though it's likely unused by volumes).
* Set the correct ownership: `RUN chown -R 1000:1000 /app/.local-storage
/app/packages/twenty-server/.local-storage /app/docker-data`.

3.  **`packages/twenty-docker/twenty/entrypoint.sh`:**
* Added a check near the beginning of the script for the presence of the
now-potentially-unused `STORAGE_LOCAL_PATH` environment variable.
* If the variable is set, a warning message is printed to standard
output, informing the user that the variable might be deprecated and
ignored if the hardcoded path change in `docker-compose.yml` is
accepted.

**Reasoning:**

By creating the target directories
(`/app/packages/twenty-server/.local-storage` and `/app/docker-data`)
within the Docker image *and* setting their ownership to `1000:1000`
during the build (while still running as root), we leverage Docker's
volume initialization behavior. When a named volume is mounted to a
non-empty directory in the container image, Docker copies the content
and ownership from the image directory into the volume. This ensures
that when the `server` and `worker` containers start (running as user
`1000`), the volumes they mount already have the correct permissions,
eliminating the need for the separate `change-vol-ownership` service.

**Regarding `STORAGE_LOCAL_PATH`:**
The `docker-compose.yml` previously allowed configuring the path for
local storage via the `STORAGE_LOCAL_PATH` variable, defaulting to
`.local-storage`. Since the Dockerfile now explicitly creates and sets
permissions for `/app/packages/twenty-server/.local-storage`,
maintaining this configuration might be unnecessary or could potentially
lead to permission errors if a user sets it to a path *not* prepared in
the Dockerfile.

This PR proposes hardcoding the path in `docker-compose.yml` to
`/app/packages/twenty-server/.local-storage` to align with the
Dockerfile changes and simplify configuration. Is this acceptable, or is
there a specific use case for retaining the `STORAGE_LOCAL_PATH`
variable that needs to be considered? If retained, the Dockerfile would
need further changes to dynamically handle permissions based on this
variable.

**Impact:**

* Improves compatibility with deployment platforms that struggle with
run-once containers.
* Simplifies the `docker-compose.yml` setup (potentially, pending
discussion on `STORAGE_LOCAL_PATH`).
* Fixes volume permissions at the source (image build) rather than
relying on a runtime fix.
* Adds a warning for users who might have the potentially deprecated
variable set.

**Testing:**

The changes have been tested locally using `docker compose up`. The
services start correctly, the application is accessible, and the warning
message for the potentially deprecated variable appears as expected when
the variable is set.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-05-20 00:14:33 +02:00

126 lines
5.0 KiB
YAML

name: twenty
services:
server:
image: twentycrm/twenty:${TAG:-latest}
volumes:
- server-local-data:/app/packages/twenty-server/.local-storage
ports:
- "3000:3000"
environment:
NODE_PORT: 3000
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
SERVER_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
STORAGE_TYPE: ${STORAGE_TYPE}
STORAGE_S3_REGION: ${STORAGE_S3_REGION}
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
# AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET}
# AUTH_GOOGLE_CALLBACK_URL: ${AUTH_GOOGLE_CALLBACK_URL}
# AUTH_GOOGLE_APIS_CALLBACK_URL: ${AUTH_GOOGLE_APIS_CALLBACK_URL}
# CALENDAR_PROVIDER_MICROSOFT_ENABLED: ${CALENDAR_PROVIDER_MICROSOFT_ENABLED}
# MESSAGING_PROVIDER_MICROSOFT_ENABLED: ${MESSAGING_PROVIDER_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_ENABLED: ${AUTH_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_CLIENT_ID: ${AUTH_MICROSOFT_CLIENT_ID}
# AUTH_MICROSOFT_CLIENT_SECRET: ${AUTH_MICROSOFT_CLIENT_SECRET}
# AUTH_MICROSOFT_CALLBACK_URL: ${AUTH_MICROSOFT_CALLBACK_URL}
# AUTH_MICROSOFT_APIS_CALLBACK_URL: ${AUTH_MICROSOFT_APIS_CALLBACK_URL}
# EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-contact@yourdomain.com}
# EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:-"John from YourDomain"}
# EMAIL_SYSTEM_ADDRESS: ${EMAIL_SYSTEM_ADDRESS:-system@yourdomain.com}
# EMAIL_DRIVER: ${EMAIL_DRIVER:-smtp}
# EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.gmail.com}
# EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-465}
# EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
depends_on:
db:
condition: service_healthy
healthcheck:
test: curl --fail http://localhost:3000/healthz
interval: 5s
timeout: 5s
retries: 20
restart: always
worker:
image: twentycrm/twenty:${TAG:-latest}
volumes:
- server-local-data:/app/packages/twenty-server/${STORAGE_LOCAL_PATH:-.local-storage}
command: ["yarn", "worker:prod"]
environment:
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
SERVER_URL: ${SERVER_URL}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
DISABLE_DB_MIGRATIONS: "true" # it already runs on the server
STORAGE_TYPE: ${STORAGE_TYPE}
STORAGE_S3_REGION: ${STORAGE_S3_REGION}
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
# AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET}
# AUTH_GOOGLE_CALLBACK_URL: ${AUTH_GOOGLE_CALLBACK_URL}
# AUTH_GOOGLE_APIS_CALLBACK_URL: ${AUTH_GOOGLE_APIS_CALLBACK_URL}
# CALENDAR_PROVIDER_MICROSOFT_ENABLED: ${CALENDAR_PROVIDER_MICROSOFT_ENABLED}
# MESSAGING_PROVIDER_MICROSOFT_ENABLED: ${MESSAGING_PROVIDER_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_ENABLED: ${AUTH_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_CLIENT_ID: ${AUTH_MICROSOFT_CLIENT_ID}
# AUTH_MICROSOFT_CLIENT_SECRET: ${AUTH_MICROSOFT_CLIENT_SECRET}
# AUTH_MICROSOFT_CALLBACK_URL: ${AUTH_MICROSOFT_CALLBACK_URL}
# AUTH_MICROSOFT_APIS_CALLBACK_URL: ${AUTH_MICROSOFT_APIS_CALLBACK_URL}
# EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-contact@yourdomain.com}
# EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:-"John from YourDomain"}
# EMAIL_SYSTEM_ADDRESS: ${EMAIL_SYSTEM_ADDRESS:-system@yourdomain.com}
# EMAIL_DRIVER: ${EMAIL_DRIVER:-smtp}
# EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.gmail.com}
# EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-465}
# EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
depends_on:
db:
condition: service_healthy
server:
condition: service_healthy
restart: always
db:
image: postgres:16
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${PG_DATABASE_USER:-postgres}
POSTGRES_PASSWORD: ${PG_DATABASE_PASSWORD:-postgres}
healthcheck:
test: pg_isready -U ${PG_DATABASE_USER:-postgres} -h localhost -d postgres
interval: 5s
timeout: 5s
retries: 10
restart: always
redis:
image: redis
restart: always
command: ["--maxmemory-policy", "noeviction"]
volumes:
db-data:
server-local-data: