PHP #

PHP adalah salah satu bahasa paling umum di dunia web, dan juga salah satu yang paling sering disalahgunakan di Docker. Image PHP di production sering > 500 MB, penuh tool build, dan bercampur antara dev dan runtime. Padahal, PHP bisa dibuat sangat ramping, aman, dan layak production — setara dengan Node.js, Python, bahkan mendekati Go — jika diperlakukan dengan benar.

Artikel ini membahas strategi Docker image PHP yang kecil dan production-grade, dengan pendekatan engineering, bukan sekadar tutorial php:apache. Kita akan lihat kenapa image PHP mudah membengkak, strategi yang benar (multi-stage + PHP-FPM + distroless), dan trade-off yang harus dipahami.

1. Realita Ukuran Image PHP #

Mari kita lihat angka realistis di production.

Setup Ukuran Image
php:apache (default) 600-800 MB
php:fpm + debian 400-600 MB
php:8.3-fpm-alpine 120-200 MB
Multi-stage + slim 90-150 MB
Multi-stage + distroless 60-100 MB

Insight: Selisih antara 800 MB dan 60 MB adalah lebih dari 10x lipat. Image PHP yang optimal bukan mustahil — hanya butuh disiplin Dockerfile yang berbeda dari kebiasaan umum.

2. Kenapa Image PHP Mudah Membengkak? #

PHP = Runtime + Web Server + Extension #

Image php:apache (atau php:*-apache) membawa Apache + PHP module + banyak extension. Untuk production modern, ini pemborosan. Apache memakan memory dan CPU yang seharusnya bisa untuk aplikasi.

Pendekatan yang lebih modern: pisahkan web server dari PHP process. PHP-FPM berjalan sebagai service terpisah, dan Nginx (atau Caddy) di depan sebagai reverse proxy. Ini lebih scalable dan lebih ramping.

Extension PHP Membengkakkan Image #

PHP punya banyak extension yang sering dibutuhkan:

  • pdo_pgsql / pdo_mysql — database driver.
  • gd — image processing.
  • intl — internationalization.
  • zip — arsip.
  • bcmath — matematika presisi tinggi.
  • opcache — bytecode cache.

Setiap extension menarik dependency OS-nya sendiri. Tanpa kontrol, semua ikut masuk image.

Composer Bocor ke Runtime #

Composer adalah dependency manager PHP. Tanpa pemisahan build vs runtime, Composer dan cache-nya akan masuk image akhir — padahal runtime tidak butuh Composer sama sekali.

Default Image php:* Penuh #

Image php:latest (atau php:8.3) membawa Debian lengkap dengan banyak utilitas. Untuk production, php:8.3-fpm-alpine atau php:8.3-fpm-slim adalah alternatif yang lebih ramping.

3. Prinsip Utama: FPM Saja, Distroless, Composer Terpisah #

Runtime image PHP idealnya hanya berisi: PHP-FPM + extension production + source code aplikasi.

Titik. Tidak ada Apache, tidak ada Composer, tidak ada extension development, tidak ada test framework, tidak ada dokumentasi.

Tiga pilar untuk mencapainya:

  1. Multi-stage build — pisahkan build (butuh Composer + dev extension) dari runtime (butuh FPM + prod extension).
  2. PHP-FPM, bukan mod_php — FPM lebih scalable, dan image php:*-fpm tidak bawa Apache.
  3. Distroless base untuk runtime — image JRE-like yang sangat ramping, tanpa shell.

4. Strategi Multi-Stage dengan Composer #

4.1 Pola Dasar: FPM + Alpine #

# ==== Stage 1: Composer Install ====
FROM composer:2.7 AS vendor

WORKDIR /app

# Copy dependency file dulu (untuk cache)
COPY composer.json composer.lock ./
RUN composer install \
    --no-dev \
    --no-scripts \
    --no-autoloader \
    --prefer-dist \
    --no-interaction

# ==== Stage 2: Build Assets (jika pakai Laravel) ====
FROM node:20-alpine AS assets

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# ==== Stage 3: Runtime ====
FROM php:8.3-fpm-alpine

WORKDIR /app

# Install production extension
RUN apk add --no-cache \
    libpq \
    icu-libs \
    oniguruma \
    libzip \
    libpng \
    libjpeg-turbo \
    freetype

# Install PHP extension
RUN docker-php-ext-install -j$(nproc) \
    pdo \
    pdo_pgsql \
    intl \
    zip \
    opcache \
    bcmath

# Copy Composer dependencies
COPY --from=vendor /app/vendor /app/vendor

# Generate optimized autoloader
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative

# Copy source code
COPY . .

# Copy built assets
COPY --from=assets /app/public/build /app/public/build

# Optimize Laravel (jika pakai)
RUN php artisan config:cache \
 && php artisan route:cache \
 && php artisan view:cache

# Non-root user
RUN addgroup -g 1001 -S appgroup \
 && adduser -u 1001 -S appuser -G appgroup
USER appuser

EXPOSE 9000
CMD ["php-fpm"]

Ukuran tipikal: 90-150 MB.

Penjelasan penting:

Stage 1: Composer

  • composer:2.7 — image Composer official, kecil dan self-contained.
  • composer install --no-dev — install hanya production dependency, skip dev (testing, debug tools).
  • --no-scripts — skip script yang mungkin jalan saat install (misal Laravel post-install).
  • --no-autoloader — skip generate autoloader; akan di-generate di stage runtime untuk optimasi.

Stage 2: Assets (opsional, hanya untuk Laravel yang pakai Vite/Mix)

  • Build JavaScript dan CSS di sini.
  • Output public/build di-copy ke runtime stage.

Stage 3: Runtime

  • php:8.3-fpm-alpine — FPM di Alpine, sudah jauh lebih ramping dari php:apache.
  • docker-php-ext-install — script resmi yang disediakan image PHP untuk install extension.
  • -j$(nproc) — paralel compile untuk mempercepat build.
  • Composer dump-autoload dengan --optimize dan --classmap-authoritative untuk autoloader yang lebih cepat.
  • php artisan config:cache dll — pre-compile konfigurasi, route, dan view.

Kapan pakai: Default untuk production service PHP. Trade-off ukuran vs debugging能力 masih seimbang — Alpine punya shell.

4.2 Versi Distroless: Production Mature #

# ==== Stage 1: Composer ====
FROM composer:2.7 AS vendor

WORKDIR /app

COPY composer.json composer.lock ./
RUN composer install \
    --no-dev \
    --no-scripts \
    --no-autoloader \
    --prefer-dist

# ==== Stage 2: PHP + Extensions ====
FROM php:8.3-cli-alpine AS builder

WORKDIR /app

RUN apk add --no-cache $PHPIZE_DEPS \
    libpq-dev \
    icu-dev \
    oniguruma-dev \
    libzip-dev \
    libpng-dev \
    libjpeg-turbo-dev \
    freetype-dev

# Build extension
RUN docker-php-ext-install -j$(nproc) \
    pdo \
    pdo_pgsql \
    intl \
    zip \
    opcache \
    bcmath \
    gd

# Copy Composer deps
COPY --from=vendor /app/vendor /app/vendor
COPY . .

# Generate optimized autoloader
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative

# ==== Stage 3: Runtime Distroless ====
FROM gcr.io/distroless/base-debian12:nonroot

WORKDIR /app

# Copy PHP + extension dari build stage
COPY --from=builder /usr/local /usr/local
COPY --from=builder /app /app

# Copy production dependencies OS
COPY --from=builder /usr/lib/x86_64-linux-gnu/libpq* /usr/lib/x86_64-linux-gnu/
COPY --from=builder /usr/lib/x86_64-linux-gnu/libicu* /usr/lib/x86_64-linux-gnu/

# Konfigurasi PHP
ENV PATH="/usr/local/bin:/usr/local/sbin:$PATH"

EXPOSE 9000

USER nonroot:nonroot

# Custom PHP-FPM config
COPY docker/php-fpm.conf /usr/local/etc/php-fpm.d/zz-app.conf
COPY docker/www.conf /usr/local/etc/php-fpm.d/www.conf

CMD ["php-fpm", "-F"]

Ukuran tipikal: 60-100 MB.

Catatan penting untuk distroless PHP:

  • Distroless tidak punya shell, tidak punya apt. Semua yang dibutuhkan runtime harus di-copy dari build stage.
  • PHP CLI + extensions di-copy dari /usr/local (tempat PHP official install di Linux).
  • Library OS yang dibutuhkan (libpq, libicu) di-copy manual dari build stage.
  • PHP-FPM config harus di-mount sebagai file, karena tidak bisa di-edit runtime.
  • php-fpm -F artinya run di foreground (penting untuk container PID 1).
  • Debugging interaktif tidak memungkinkan — observability harus solid.

Kapan pakai: Production high-maturity, security-first, ukuran kritis.

5. Memilih Base Image PHP #

Base Image Ukuran Catatan
php:8.3-apache 600-800 MB Apache + mod_php + Debian, jangan pakai production
php:8.3-fpm 400-600 MB PHP-FPM di Debian, default yang aman
php:8.3-fpm-alpine 120-200 MB PHP-FPM di Alpine, kompromi terbaik
php:8.3-fpm-slim 200-300 MB PHP-FPM di Debian slim
Distroless + custom 60-100 MB Paling ramping, butuh setup lebih

Rekomendasi:

  • Default: php:8.3-fpm-alpine — ukuran wajar, debugging能力.
  • Production high-maturity: Distroless — ukuran kecil, observability solid.
  • Development: php:8.3-cli-alpine — ringan, ada composer built-in.

6. Extension Management #

Install Extension Resmi #

Image PHP official menyediakan script docker-php-ext-install untuk install extension yang bundled dengan PHP source:

RUN docker-php-ext-install \
    pdo \
    pdo_pgsql \
    intl \
    zip \
    opcache

Install Extension dengan PECL #

Untuk extension yang tidak bundled (misal Redis, APCu, Imagick), pakai PECL:

RUN pecl install redis-6.0.2 \
 && docker-php-ext-enable redis

Install dari Source #

Untuk extension custom, compile dari source:

RUN apk add --no-cache $PHPIZE_DEPS \
    librdkafka-dev \
 && pecl install rdkafka \
 && docker-php-ext-enable rdkafka \
 && apk del $PHPIZE_DEPS

Penting: Selalu bersihkan $PHPIZE_DEPS setelah install untuk mengecilkan image.

Production vs Dev Extension #

Pisahkan extension yang dibutuhkan runtime dari yang hanya untuk development:

Production wajib:

  • opcache — bytecode cache, wajib untuk performa.
  • pdo + driver database (pdo_pgsql, pdo_mysql).
  • intl — internationalization.
  • mbstring — string handling.
  • bcmath — matematika presisi.
  • zip / gd / imagick — sesuai kebutuhan aplikasi.

Development only (jangan di production):

  • xdebug — debugger, menambah ~10 MB.
  • pcov / xdebug — code coverage.
  • dev package dari apk add (seperti gcc, make).

7. Composer Optimization #

Production Install Flag #

composer install \
  --no-dev \                  # Skip dev dependency
  --no-scripts \              # Skip composer scripts
  --prefer-dist \             # Pakai dist archive, bukan clone git
  --classmap-authoritative \  # Autoloader strict, lebih cepat
  --no-interaction \          # Non-interactive mode
  --optimize-autoloader       # Optimasi classmap

Bonus: Tambahkan --no-progress untuk log CI yang lebih bersih.

Autoloader Optimization #

Setelah install, generate autoloader yang optimal:

composer dump-autoload \
  --optimize \                # PSR-0/PSR-4 -> classmap
  --no-dev \                  # Skip dev class
  --classmap-authoritative    # Class tidak akan di-cari di PSR-4 lagi

Untuk Laravel, ini bisa menurunkan memory usage dan mempercepat bootstrap.

Autoload Cache di CI #

Untuk build yang lebih cepat, cache Composer cache:

# GitHub Actions
- name: Cache Composer
  uses: actions/cache@v4
  with:
    path: ~/.composer/cache
    key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
    restore-keys: ${{ runner.os }}-composer-

8. PHP Framework (Laravel / Symfony) #

Laravel Optimization #

Di build stage, jalankan optimasi Laravel:

# Pre-compile konfigurasi, route, view
RUN php artisan config:cache \
 && php artisan route:cache \
 && php artisan view:cache

Pastikan APP_ENV=production saat compile agar cache yang dihasilkan sesuai dengan production.

Trick: php artisan optimize menjalankan semua cache di atas sekaligus.

Penting: Runtime image tidak boleh punya Node.js, npm, atau frontend build tools. Asset harus sudah di-compile di build stage dan di-copy ke runtime.

Symfony Optimization #

Untuk Symfony:

# Pre-compile container
RUN APP_ENV=prod php bin/console cache:clear --no-warmup \
 && APP_ENV=prod php bin/console cache:warmup

Atau untuk production build yang lebih ketat, pakai Composer autoloader optimization.

9. PHP-FPM Configuration #

Default PHP-FPM configuration sering tidak optimal untuk container. Buat custom config:

docker/php-fpm.d/zz-app.conf:

[www]
listen = 0.0.0.0:9000
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 5
pm.max_requests = 1000

Tuning untuk container:

  • pm.max_children — harus sesuai memory limit container. Formula: (container_memory_limit) / (avg_php_memory_per_child).
  • pm.max_requests — set agar worker di-recycle secara berkala untuk mencegah memory leak.
  • pm = dynamic lebih fleksibel dari pm = static untuk traffic variabel.

OPCache configuration:

; /usr/local/etc/php/conf.d/opcache.ini
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.fast_shutdown=1

Untuk production, opcache.validate_timestamps=0 artinya container harus di-restart saat kode berubah (immutable deployment). Ini sesuai dengan pola container.

10. Logging #

PHP-FPM default log ke /var/log/php-fpm.log. Untuk container, redirect ke STDOUT/STDERR:

docker/php-fpm.d/zz-app.conf:

[global]
error_log = /proc/self/fd/2
log_level = warning

[www]
access.log = /proc/self/fd/1
catch_workers_output = yes
  • /proc/self/fd/1 = STDOUT
  • /proc/self/fd/2 = STDERR

Untuk Laravel, konfigurasi logging di config/logging.php agar pakai single channel dengan output STDOUT:

'channels' => [
    'stderr' => [
        'driver' => 'monolog',
        'level' => 'debug',
        'handler' => Monolog\Handler\StreamHandler::class,
        'with' => [
            'stream' => 'php://stderr',
        ],
    ],
],

11. Web Server Terpisah #

Best practice: Pisahkan PHP-FPM dari web server. Jangan pakai php:apache.

# docker-compose.yml
services:
  app:
    image: myapp:latest
    expose:
      - "9000"
  
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./public:/var/www/html/public:ro
    depends_on:
      - app

Keuntungan:

  • Image PHP lebih ramping (tanpa Nginx/Apache).
  • Scaling independen (Nginx bisa di-scale terpisah dari PHP-FPM).
  • Web server bisa di-cache (Nginx cache static asset).
  • Nginx static asset serving lebih cepat dari PHP.

12. Production-Grade Logging #

// config/logging.php (Laravel)
'channels' => [
    'production' => [
        'driver' => 'stack',
        'channels' => ['stdout_json'],
    ],
    'stdout_json' => [
        'driver' => 'monolog',
        'handler' => Monolog\Handler\StreamHandler::class,
        'with' => [
            'stream' => 'php://stdout',
        ],
        'formatter' => Monolog\Formatter\JsonFormatter::class,
    ],
],

Konfigurasi ini:

  • Log ke STDOUT (bukan file).
  • Format JSON (mudah di-parse aggregator).
  • Include trace ID jika pakai OpenTelemetry.

13. Security Hardening #

Non-Root User #

# Untuk alpine
RUN addgroup -g 1001 -S appgroup \
 && adduser -u 1001 -S appuser -G appgroup
USER appuser

# Untuk distroless, sudah ada nonroot
USER nonroot:nonroot

Permission File #

Pastikan file source code dimiliki user yang akan menjalankan PHP:

COPY --chown=appuser:appgroup . /app

Jangan Expose .env #

.env harus di-mount saat runtime, tidak pernah di-bake ke image.

# docker-compose.yml
services:
  app:
    env_file: .env

Pastikan .env ada di .dockerignore untuk jaga-jaga:

# .dockerignore
.env
.env.*

Vulnerability Scanning #

- name: Build
  run: docker build -t myapp:${{ github.sha }} .
- name: Scan
  run: trivy image --exit-code 1 --severity CRITICAL myapp:${{ github.sha }}

PHP image biasanya menarik banyak package OS (library untuk extension). Rebuild berkala untuk menarik patch.

14. Anti-Pattern yang Harus Dihindari #

✗ Pakai php:apache #

// ✗ Image 600+ MB, Apache tidak scalable
FROM php:8.3-apache
COPY . /var/www/html/

Solusi: Pisahkan web server dari PHP-FPM. Pakai php:8.3-fpm-alpine.

✗ Composer di Runtime #

// ✗ Composer dan cache-nya ada di runtime
FROM php:8.3-fpm
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY . .
RUN composer install

Solusi: Multi-stage. Composer di build stage, copy vendor/ saja ke runtime.

✗ Install Dev Dependency #

// ✗ phpunit, debug tools ikut ke production
RUN composer install

Solusi: composer install --no-dev di production build.

✗ Build Tools Bocor #

// ✗ gcc, make, header file masuk runtime image
FROM php:8.3-fpm-alpine
RUN apk add $PHPIZE_DEPS gcc make
RUN docker-php-ext-install pdo_pgsql

Solusi: Build stage punya build tools, runtime stage tidak.

✗ Tidak Ada OPcache #

// ✗ PHP di-interpret setiap request (lambat)
FROM php:8.3-fpm

Solusi: Selalu install dan enable OPcache di production.

✗ Tag Tanpa Versi #

// ✗ Build tidak deterministik
FROM php:latest
FROM php:8

Solusi: Pin tag: php:8.3.11-fpm-alpine3.20.

15. Contoh Dockerfile PHP Production-Grade (Laravel) #

# syntax=docker/dockerfile:1.7

# ==== Stage 1: Composer ====
FROM composer:2.7.7 AS vendor

WORKDIR /app

COPY composer.json composer.lock ./
RUN composer install \
    --no-dev \
    --no-scripts \
    --no-autoloader \
    --prefer-dist \
    --no-interaction

# ==== Stage 2: Frontend Assets ====
FROM node:20-alpine AS assets

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# ==== Stage 3: Runtime ====
FROM php:8.3.11-fpm-alpine3.20

WORKDIR /app

# Install production extension
RUN apk add --no-cache \
    libpq \
    icu-libs \
    oniguruma \
    libzip \
    libpng \
    libjpeg-turbo \
    freetype

RUN docker-php-ext-install -j$(nproc) \
    pdo \
    pdo_pgsql \
    intl \
    zip \
    opcache \
    bcmath

# OPcache config untuk production
COPY docker/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# PHP-FPM config
COPY docker/php-fpm.conf /usr/local/etc/php-fpm.d/zz-app.conf

# Copy Composer dependencies
COPY --from=vendor /app/vendor /app/vendor

# Generate optimized autoloader
RUN composer dump-autoload --optimize --no-dev --classmap-authoritative

# Copy source code
COPY . .

# Copy built assets
COPY --from=assets /app/public/build /app/public/build

# Laravel optimization
ENV APP_ENV=production
RUN php artisan config:cache \
 && php artisan route:cache \
 && php artisan view:cache

# Non-root user
RUN addgroup -g 1001 -S appgroup \
 && adduser -u 1001 -S appuser -G appgroup
USER appuser

EXPOSE 9000

CMD ["php-fpm"]

Karakteristik:

  • Ukuran: 90-150 MB.
  • Composer hanya di build stage.
  • Frontend assets di build terpisah.
  • PHP-FPM (bukan mod_php).
  • OPcache optimal.
  • Laravel pre-compiled.
  • Non-root user.

16. Kapan Pakai Strategi Mana #

Kondisi Pilihan Alasan
Laravel API standar fpm-alpine + multi-stage Ukuran wajar, debugging能力
Production high-maturity Distroless Ukuran minimal, observability solid
WordPress / CMS fpm-alpine + custom config Butuh tool, plugin flexibility
Microservice Distroless Image kecil, attack surface minimal
Legacy dengan banyak extension fpm-slim Butuh glibc, debugging能力
Serverless Distroless atau alpine Cold start time penting

17. Checklist Review Dockerfile PHP #

BASE IMAGE:
  □ Tag eksplisit (php:8.3.11-fpm-alpine3.20, bukan latest)
  □ Pakai FPM, bukan apache
  □ Pakai alpine atau slim, bukan default debian
  □ Bukan php:latest (terlalu besar)

BUILD:
  □ Multi-stage build
  □ Composer di build stage, copy vendor/ saja
  □ composer install --no-dev --optimize-autoloader
  □ Frontend assets di build terpisah (jika perlu)
  □ Laravel optimize / Symfony cache di build
  □ Build tool (gcc, make) hanya di build stage

EXTENSION:
  □ Production extension hanya (no xdebug, no pcov)
  □ docker-php-ext-install -j$(nproc) untuk paralel
  □ apk del $PHPIZE_DEPS setelah install

RUNTIME:
  □ USER nonroot
  □ OPcache aktif dan dioptimasi
  □ PHP-FPM config dituning
  □ Log ke STDOUT
  □ CMD dalam exec form

UKURAN:
  □ < 200 MB untuk fpm-alpine
  □ < 100 MB untuk distroless
  □ docker history tidak ada layer aneh

KEAMANAN:
  □ Tidak ada secret di image
  □ .dockerignore tegas
  □ .env di-exclude
  □ Image di-scan trivy/grype
  □ Non-root user
  □ Base image up-to-date

FRAMEWORK:
  □ Laravel: config:cache, route:cache, view:cache
  □ Symfony: cache:clear --no-warmup + cache:warmup
  □ Composer: --classmap-authoritative --optimize

Ringkasan #

  • Image PHP ramping sangat mungkin — bukan legacy. Yang membedakan image 800 MB dengan image 90 MB adalah pilihan FPM (bukan apache), multi-stage, dan Composer terpisah.
  • Realita ukuran: 60-100 MB (distroless), 90-150 MB (multi-stage + alpine), 120-200 MB (fpm-alpine), 400-600 MB (fpm default), 600-800 MB (apache — anti-pattern).
  • Pakai PHP-FPM, bukan mod_php/apache — pisahkan web server dari PHP. Image lebih ramping, scaling lebih fleksibel.
  • Multi-stage build wajib — Composer di build stage, copy vendor/ saja ke runtime. Composer dan cache-nya tidak boleh masuk runtime.
  • Composer install –no-dev –optimize-autoloader –classmap-authoritative — skip dev, optimalkan classmap, dan jangan generate autoloader yang lambat.
  • Pisahkan dev vs prod extension — OPcache wajib production, xdebug/pcov hanya development. Build tool (gcc, make) hanya di build stage.
  • Laravel/Symfony optimization — pre-compile config, route, view di build stage. Runtime tidak boleh punya Node.js atau build tools.
  • OPcache konfigurasi optimalvalidate_timestamps=0 untuk immutable deployment (container di-restart saat kode berubah).
  • Web server terpisah — Nginx di depan, PHP-FPM di belakang. Scaling independen, static asset lebih cepat.
  • Log ke STDOUT/proc/self/fd/1 dan /proc/self/fd/2 untuk PHP-FPM. Laravel: konfigurasi monolog dengan php://stdout.
  • Tag eksplisitphp:8.3.11-fpm-alpine3.20, bukan latest. Reproducibility penting untuk audit dan rollback.
  • Image ramping butuh observability solid — log JSON, metrics, healthcheck, graceful shutdown. PHP bisa se-ramping Go dengan disiplin Dockerfile yang tepat.

← Sebelumnya: Java (Spring Boot)   Berikutnya: TypeScript →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact