콘텐츠로 건너뛰기

Render에서 PHP/Node.js 파일 업로드 설정하는 방법 | Disk·환경 변수·S3 연동

카테고리: Render·배포 설정

Render는 Heroku의 대체제로 인기 있는 클라우드 플랫폼입니다. Vercel이나 Netlify와 달리 서버리스가 아닌 상주 프로세스(Web Service) 모델을 사용하므로 파일 업로드 아키텍처가 다릅니다. 본 문서에서는 Render의 영구 스토리지(Disk) 설정, PHP 환경에서 upload_max_filesize 수정 방법, 환경 변수를 사용한 S3 설정, Infrastructure as Code(IaC)의 render.yaml(Blueprint) 설정 예시까지 설명합니다.

Client Render Web Service App (always-on) Disk (persistent) /var/data — single instance No body size limit (set in app/proxy) S3 / R2 (recommended) scale-out friendly
그림: Render Web Service와 업로드 저장 대상 (Disk vs S3)

Render Disk(영구 스토리지)란?

Render의 Web Service는 기본적으로 에피메랄(재배포 시 파일 시스템 리셋)입니다. 업로드된 파일을 유지하려면 Disk를 추가합니다.

스토리지의 종류 영속성 여러 인스턴스 간 공유 비용
임시 디스크(기본값) 재배포시 리셋 불가능 무료
Render Disk (영구) 영구적(수동 삭제까지 유지) 불가능 (1인스턴스만 가능) $0.25/GB/월
AWS S3 (외부) 영구적 지원됨 S3 요금 준수

Render Disk는 스케일 아웃(다중 인스턴스)을 지원하지 않으므로 프로덕션 파일 업로드에는 S3 연동이 권장됩니다. Disk는 개발, 스테이징 환경 또는 단일 인스턴스 배포에 적합합니다.

Render Dashboard에서 Disk 설정

Disk는 Render Dashboard의 Web Service 설정에서 추가할 수 있습니다.

# render.yaml(Blueprint)での Disk 設定
services:
  - type: web
    name: my-app
    runtime: node
    buildCommand: npm install && npm run build
    startCommand: npm start
    disk:
      name: uploads-disk
      mountPath: /app/uploads  # アプリ内でのマウントパス
      sizeGB: 10               # 10GB のディスク
    envVars:
      - key: UPLOAD_DIR
        value: /app/uploads
// Node.js での Disk へのファイル保存
const path = require('path');
const fs = require('fs');

const UPLOAD_DIR = process.env.UPLOAD_DIR || path.join(__dirname, 'uploads');

// アップロードディレクトリが存在しない場合は作成
if (!fs.existsSync(UPLOAD_DIR)) {
  fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}

// multer の設定例(Disk ストレージ)
const multer = require('multer');

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, UPLOAD_DIR);
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
    const ext = path.extname(file.originalname);
    cb(null, `upload-${uniqueSuffix}${ext}`);
  },
});

const upload = multer({
  storage,
  limits: { fileSize: 50 * 1024 * 1024 }, // 50MB
  fileFilter: (req, file, cb) => {
    const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
    if (allowedTypes.includes(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error('許可されていないファイル形式です。'));
    }
  },
});

PHP 환경에서의 upload_max_filesize 설정

Render의 PHP 앱(Docker 기반)의 경우 php.ini 설정을 수정하여 업로드 크기를 조정할 수 있습니다. Render는 임의의 Docker 설정을 지원하므로 다른 호스팅 서비스보다 유연합니다.

# Dockerfile(PHP アプリ)
FROM php:8.2-fpm

# 必要な拡張をインストール
RUN docker-php-ext-install pdo pdo_mysql

# php.ini の設定をオーバーライド
RUN echo "upload_max_filesize = 100M" > /usr/local/etc/php/conf.d/uploads.ini \
 && echo "post_max_size = 110M" >> /usr/local/etc/php/conf.d/uploads.ini \
 && echo "memory_limit = 256M" >> /usr/local/etc/php/conf.d/uploads.ini \
 && echo "max_execution_time = 120" >> /usr/local/etc/php/conf.d/uploads.ini \
 && echo "max_input_time = 120" >> /usr/local/etc/php/conf.d/uploads.ini

WORKDIR /var/www/html
COPY . .

EXPOSE 9000
CMD ["php-fpm"]
# php.ini(直接配置する場合)
; ファイルアップロードの有効化
file_uploads = On

; 1ファイルあたりの上限
upload_max_filesize = 100M

; POST データ全体の上限(upload_max_filesize より大きくする)
post_max_size = 110M

; PHP スクリプトのメモリ上限
memory_limit = 256M

; スクリプトの最大実行時間(秒)
max_execution_time = 120

; ファイル入力待ちの最大時間(秒)
max_input_time = 120

; 一時ファイルのディレクトリ(Render の場合は /tmp を使用)
upload_tmp_dir = /tmp
<?php
// .htaccess が使えない場合は ini_set() で実行時に変更(共有ホスティングでは制限あり)
// Render(Docker)では Dockerfile での設定が確実

// ini_set() による実行時オーバーライド(一部の設定のみ有効)
ini_set('memory_limit', '256M');
// upload_max_filesize と post_max_size は実行時変更不可(php.ini での設定が必要)

// 設定値の確認
echo ini_get('upload_max_filesize'); // "100M"
echo ini_get('post_max_size');       // "110M"
echo ini_get('memory_limit');        // "256M"

환경 변수를 사용한 S3 설정

Render Dashboard 또는 render.yaml에서 환경 변수를 설정하고 앱에서 AWS S3에 액세스합니다. Render의 시크릿 관리 기능을 사용하여 민감한 정보를 관리하고, render.yaml에는 키만 기재합니다.

# render.yaml(Blueprint)— シークレットはキーのみ定義
services:
  - type: web
    name: my-php-app
    runtime: docker
    dockerfilePath: ./Dockerfile
    envVars:
      # 非シークレット(値を直接記載)
      - key: AWS_REGION
        value: ap-northeast-1
      - key: AWS_S3_BUCKET
        value: my-app-uploads

      # シークレット(Render Dashboard で値を設定)
      - key: AWS_ACCESS_KEY_ID
        sync: false          # sync: false でダッシュボードで手動入力
      - key: AWS_SECRET_ACCESS_KEY
        sync: false

      # または Render の Environment Group を参照
      - fromGroup: aws-credentials

    disk:
      name: tmp-uploads
      mountPath: /tmp/uploads
      sizeGB: 5
<?php
// PHP での AWS SDK を使った S3 アップロード
require 'vendor/autoload.php';

use Aws\S3\S3Client;
use Aws\Exception\AwsException;

$s3 = new S3Client([
    'version' => 'latest',
    'region'  => getenv('AWS_REGION'),
    'credentials' => [
        'key'    => getenv('AWS_ACCESS_KEY_ID'),
        'secret' => getenv('AWS_SECRET_ACCESS_KEY'),
    ],
]);

function uploadToS3(array $file, string $bucket): array
{
    global $s3;

    $key = 'uploads/' . uniqid('file_', true) . '_' . basename($file['name']);

    try {
        $result = $s3->putObject([
            'Bucket'      => $bucket,
            'Key'         => $key,
            'SourceFile'  => $file['tmp_name'],
            'ContentType' => mime_content_type($file['tmp_name']),
            'ACL'         => 'private',
        ]);

        return [
            'success' => true,
            'key'     => $key,
            'url'     => $result['ObjectURL'],
        ];
    } catch (AwsException $e) {
        return ['success' => false, 'error' => $e->getMessage()];
    }
}

// アップロード処理
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
    $file = $_FILES['file'];

    // バリデーション
    $maxSize = 50 * 1024 * 1024; // 50MB
    if ($file['size'] > $maxSize) {
        http_response_code(413);
        echo json_encode(['error' => 'ファイルが大きすぎます(上限50MB)。']);
        exit;
    }

    $allowedMimes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
    $detectedMime = mime_content_type($file['tmp_name']); // サーバー側でMIMEを検出
    if (!in_array($detectedMime, $allowedMimes)) {
        http_response_code(415);
        echo json_encode(['error' => '許可されていないファイル形式です。']);
        exit;
    }

    $result = uploadToS3($file, getenv('AWS_S3_BUCKET'));
    echo json_encode($result);
}

render.yaml(Blueprint)의 완전한 설정 예제

Blueprint를 사용하면 Render의 인프라를 코드로 관리할 수 있습니다. Web Service·데이터베이스·Redis·Disk·환경 변수를 함께 정의할 수 있습니다.

# render.yaml
version: "1"

services:
  # Web アプリケーション
  - type: web
    name: file-upload-app
    runtime: node
    plan: starter           # free / starter / standard / pro
    region: singapore       # oregon / frankfurt / singapore / ohio
    buildCommand: npm ci && npm run build
    startCommand: node server.js
    healthCheckPath: /health
    autoDeploy: true        # Git push で自動デプロイ

    # 永続ストレージ(スケールアウト不可)
    disk:
      name: user-uploads
      mountPath: /app/data/uploads
      sizeGB: 20

    # 環境変数
    envVars:
      - key: NODE_ENV
        value: production
      - key: PORT
        value: 3000
      - key: UPLOAD_DIR
        value: /app/data/uploads
      - key: AWS_REGION
        value: ap-northeast-1
      - key: AWS_S3_BUCKET
        value: my-production-bucket

      # シークレット(ダッシュボードで値を設定)
      - key: AWS_ACCESS_KEY_ID
        sync: false
      - key: AWS_SECRET_ACCESS_KEY
        sync: false
      - key: DATABASE_URL
        fromDatabase:
          name: app-db
          property: connectionString

  # PostgreSQL データベース
  - type: pserv
    name: app-db
    runtime: postgres
    plan: starter
    region: singapore
    databaseName: app_production

❓ 자주 묻는 질문

Render 무료 플랜에서 파일 업로드를 쓸 수 있나요?
Render의 무료 플랜(Free)에서는 Disk(영구 스토리지)를 추가할 수 없습니다. Disk는 Starter 플랜 이상($7/월~)에서 이용 가능하며 $0.25/GB/월의 추가 요금이 듭니다. 무료 플랜에서 파일을 영속화하려면 S3 등 외부 스토리지 서비스를 써야 합니다. 또한 무료 플랜 서비스는 일정 시간 접근이 없으면 슬립하므로 업로드된 임시 파일도 사라집니다.
Render에서 PHP의 upload_max_filesize를 변경하려면?
Render는 Docker로 앱을 배포하므로 Dockerfile 안에서 php.ini 설정을 변경할 수 있습니다. RUN echo 'upload_max_filesize = 100M' > /usr/local/etc/php/conf.d/uploads.ini 같은 명령을 추가하거나 커스텀 php.ini 파일을 COPY 해 포함시킵니다. upload_max_filesize와 post_max_size는 실행 시(ini_set)에는 변경할 수 없으므로 반드시 Dockerfile이나 php.ini 파일에서 설정합니다.
Render의 Disk와 S3 중 무엇을 써야 하나요?
단일 인스턴스의 소규모 운영이나 개발 환경이라면 Render Disk가 간단합니다. 그러나 스케일아웃(여러 인스턴스)이 필요하거나 CDN 배포·서명된 URL·버전 관리가 필요하면 S3를 씁니다. S3는 비용($0.023/GB/월)도 Disk($0.25/GB/월)보다 저렴하므로 파일 크기가 크면 S3가 경제적입니다.

이 기사에서 사용할 수 있는 테스트 파일 (무료)