symfony-opcua · master
Docs · Recipes

Dev with Docker

Local Symfony dev with the OPC UA test-suite as a Docker sidecar. Docker Compose, the env wiring, and the simplest dev loop.

A typical Symfony app benefits from running the OPC UA test suite as a local sidecar. Two containers, one docker compose up, full dev environment.

docker-compose.yml

text docker-compose.yml
services:
  app:
    build:
      context: .
      target: app-dev
    volumes:
      - .:/var/www/html
    ports:
      - "8000:80"
    environment:
      APP_ENV: dev
      DATABASE_URL: postgresql://app:app@db:5432/app
      OPCUA_ENDPOINT: opc.tcp://opcua-test:4840
      OPCUA_USERNAME: admin
      OPCUA_PASSWORD: admin123
    depends_on:
      - db
      - opcua-test

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER:     app
      POSTGRES_PASSWORD: app
      POSTGRES_DB:       app
    ports:
      - "5432:5432"

  opcua-test:
    image: ghcr.io/php-opcua/uanetstandard-test-suite:v1.2.0
    ports:
      - "4840:4840"   # No security
      - "4841:4841"   # Secured
    healthcheck:
      test: ["CMD", "sh", "-c", "nc -z localhost 4840"]
      interval: 5s
      timeout: 2s
      retries: 10

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

Bring it up:

bash terminal
docker compose up -d

.env in the Symfony app

bash .env
APP_ENV=dev
APP_DEBUG=true

DATABASE_URL=postgresql://app:app@db:5432/app
MESSENGER_TRANSPORT_DSN=redis://redis:6379/messages
MERCURE_URL=http://mercure:80/.well-known/mercure
MERCURE_PUBLIC_URL=http://localhost:3000/.well-known/mercure

OPCUA_ENDPOINT=opc.tcp://opcua-test:4840
OPCUA_USERNAME=admin
OPCUA_PASSWORD=admin123

Note: hostnames are service names (opcua-test, db), not localhost — Docker Compose's internal DNS resolves them.

Adding Mercure

text add Mercure
services:
  mercure:
    image: dunglas/mercure:latest
    environment:
      SERVER_NAME: ':80'
      MERCURE_PUBLISHER_JWT_KEY: '!ChangeMe!'
      MERCURE_SUBSCRIBER_JWT_KEY: '!ChangeMe!'
    ports:
      - "3000:80"

Running tests against the suite

bash tests
docker compose exec app php bin/console doctrine:database:create --env=test
docker compose exec app php bin/console doctrine:migrations:migrate --env=test --no-interaction

# Unit + functional
docker compose exec app vendor/bin/phpunit --testsuite=unit
docker compose exec app vendor/bin/phpunit --testsuite=functional

# Integration (against opcua-test)
docker compose exec app vendor/bin/phpunit --testsuite=integration

The integration tests target opcua-test:4840 (inside the network) or localhost:4840 (from the host) — both work.

Symfony binary alternative

If you prefer running PHP locally and Docker only for services:

bash symfony binary
# Start just the OPC UA test server + db + redis
docker compose up -d opcua-test db redis

# Run Symfony locally (uses the Symfony binary's PHP)
symfony serve --dir=public

.env then targets localhost:4840 instead of opcua-test:4840.

Running the daemon locally

Inside the Symfony app container:

bash daemon
docker compose exec app php bin/console opcua:session

The daemon binds to var/opcua-session-manager.sock inside the container. With the host bind-mount, the socket is visible at ./var/opcua-session-manager.sock on the host.

Multi-container Symfony app + daemon + workers

text extended compose
services:
  app:        # FPM
    # ... as before

  daemon:
    build:
      context: .
      target: app-dev
    command: php bin/console opcua:session
    volumes:
      - .:/var/www/html
    environment:
      APP_ENV: dev
      OPCUA_ENDPOINT: opc.tcp://opcua-test:4840
      OPCUA_SOCKET_PATH: /var/www/html/var/opcua-session-manager.sock
    depends_on:
      opcua-test:
        condition: service_healthy

  worker:
    build:
      context: .
      target: app-dev
    command: php bin/console messenger:consume async_opcua --time-limit=3600
    volumes:
      - .:/var/www/html
    environment:
      APP_ENV: dev
      MESSENGER_TRANSPORT_DSN: redis://redis:6379/messages
    depends_on:
      - daemon
      - redis

app, daemon, and worker all share the same image and the same Symfony codebase. Different commands run different roles.

A useful Makefile

text Makefile
.PHONY: up down logs sh tests

up:
	docker compose up -d

down:
	docker compose down

logs:
	docker compose logs -f

sh:
	docker compose exec app bash

tests:
	docker compose exec app vendor/bin/phpunit

migrate:
	docker compose exec app php bin/console doctrine:migrations:migrate --no-interaction

Trust-store flow under Docker

The trust store lives in the bind-mounted directory, so both the container and host see the same files. Pin the test server's cert using the opcua-cli companion package (or your own app:trust:add Symfony command — see Security · Trust store):

bash terminal
docker compose exec app vendor/bin/opcua trust:add \
    opc.tcp://opcua-test:4841

Pinned certs land in ./var/opcua-trust/ on the host.

CI compose

For CI runs, use docker-compose.ci.yml with the test suite:

text docker-compose.ci.yml
services:
  opcua-test:
    image: ghcr.io/php-opcua/uanetstandard-test-suite:v1.2.0
    ports:
      - "4840:4840"
      - "4841:4841"

…then in GitHub Actions:

text workflow
services:
  opcua-test:
    image: ghcr.io/php-opcua/uanetstandard-test-suite:v1.2.0
    ports: ['4840:4840', '4841:4841']
    options: >-
      --health-cmd "nc -z localhost 4840"
      --health-interval 5s

Tearing down

bash terminal
docker compose down
docker compose down -v   # also remove volumes
Documentation