๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Spring & SpringBoot

Docker + Spring Boot + Nginx๋กœ ๋‚˜๋งŒ์˜ ์ด๋ฏธ์ง€ ํŒŒ์ผ ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

by ์ฐฝ๋”ฐ์˜ค 2025. 5. 2.
728x90

๐Ÿ–ผ๏ธ ์„œ๋ก : ์™œ ๊ฐœ์ธ ํŒŒ์ผ ์„œ๋ฒ„์ธ๊ฐ€?

ํ”„๋กœ์ ํŠธ๋ฅผ ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ , URL๋กœ ์ ‘๊ทผํ•ด์„œ ๋ณด์—ฌ์ค˜์•ผ ํ•  ์ผ์ด ๋งŽ์Šต๋‹ˆ๋‹ค.
AWS S3 ๊ฐ™์€ ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€๋„ ์ข‹์ง€๋งŒ, ๊ฐ„๋‹จํ•œ ๋‚ด๋ถ€ ์„œ๋น„์Šค๋‚˜ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ๋Š” ์ง์ ‘ ๊ตฌ์ถ•ํ•œ ํŒŒ์ผ ์„œ๋ฒ„๊ฐ€ ๋” ๊ฐ„ํŽธํ•˜๊ณ  ๋น„์šฉ๋„ ๋“ค์ง€ ์•Š์ฃ .

๊ทธ๋ž˜์„œ ์ด๋ฒˆ์—๋Š” Spring Boot + Docker + Nginx ์กฐํ•ฉ์œผ๋กœ ๊ฐœ์ธ ์ด๋ฏธ์ง€ ํŒŒ์ผ ์„œ๋ฒ„๋ฅผ ๊ตฌ์ถ•ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

์ „์ฒด๊ตฌ์กฐ

 

[Client]
   |
[Spring Boot]  ← ํŒŒ์ผ ์—…๋กœ๋“œ + ๋””๋ ‰ํ† ๋ฆฌ ์ž๋™ ์ƒ์„ฑ
   |
[Docker Volume] (๊ณต์œ )
   |
[Nginx]       → ์ •์  ํŒŒ์ผ ์„œ๋ฒ„ (์ด๋ฏธ์ง€ ์„œ๋น™)

 


โš™๏ธ 1. Spring Boot: ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ + ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ

Spring Boot๋Š” ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ›์•„,
์‚ฌ์šฉ์ž ID๋ณ„ ๋””๋ ‰ํ† ๋ฆฌ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์—†๋‹ค๋ฉด ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

 

 

๐Ÿ“ FileUploadController.java

 

@RestController
@RequestMapping("/api/files")
public class FileUploadController {

    private final FileUploadService fileUploadService;

    public FileUploadController(FileUploadService fileUploadService) {
        this.fileUploadService = fileUploadService;
    }

    @PostMapping("/upload/{userId}")
    public ResponseEntity<String> uploadFile(
            @PathVariable String userId,
            @RequestParam("file") MultipartFile file) {
        try {
            String savedPath = fileUploadService.saveFile(userId, file);
            return ResponseEntity.ok("File saved at: " + savedPath);
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                                 .body("File upload failed");
        }
    }
}

 


๐Ÿ“ FileUploadService.java

 

@Service
public class FileUploadService {

    private final String uploadRoot = "/app/uploads/users";

    public String saveFile(String userId, MultipartFile file) throws IOException {
        Path userDir = Paths.get(uploadRoot, userId);
        if (Files.notExists(userDir)) {
            Files.createDirectories(userDir);
        }
        String originalFilename = file.getOriginalFilename();
        Path targetPath = userDir.resolve(originalFilename);
        file.transferTo(targetPath.toFile());
        return targetPath.toString();
    }
}

 

๐Ÿ“„ application.yml

 

spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 10MB

 


๐Ÿณ 2. Docker + docker-compose ๊ตฌ์„ฑ

Spring Boot ์ปจํ…Œ์ด๋„ˆ์™€ Nginx ์ปจํ…Œ์ด๋„ˆ๊ฐ€ **๊ฐ™์€ ๋ณผ๋ฅจ(/uploads)**์„ ๊ณต์œ ํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

 

version: '3'
services:
  app:
    build: .
    volumes:
      - ./uploads:/app/uploads
    ports:
      - "8080:8080"

  nginx:
    image: nginx
    volumes:
      - ./uploads:/usr/share/nginx/html:ro
    ports:
      - "80:80"

๐ŸŒ 3. Nginx: ์ •์  ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ œ๊ณต

Nginx๋Š” uploads ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋งˆ์šดํŠธํ•ด์„œ
http://localhost/users/1234/profile.jpg์ฒ˜๋Ÿผ ์ •์  ํŒŒ์ผ์„ ์ง์ ‘ ์„œ๋น™ํ•ฉ๋‹ˆ๋‹ค.

 


๐Ÿงช 4. ํ…Œ์ŠคํŠธํ•˜๊ธฐ

โœ… ํŒŒ์ผ ์—…๋กœ๋“œ

curl -F "file=@profile.jpg" http://localhost:8080/api/files/upload/1234

โœ… ์ด๋ฏธ์ง€ ์ ‘๊ทผ

http://localhost/users/1234/profile.jpg

 

 

โœ… ๋งˆ๋ฌด๋ฆฌ

์ด ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  • ๊ฐ„๋‹จํ•˜๊ณ  ๋กœ์ปฌ ํ…Œ์ŠคํŠธ์— ์œ ๋ฆฌ
  • ๋น„์šฉ์ด ๋“ค์ง€ ์•Š์Œ
  • Nginx๊ฐ€ ํŒŒ์ผ์„ ๋น ๋ฅด๊ฒŒ ์„œ๋น™

ํ•˜์ง€๋งŒ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ ๋„ ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ๋ฐฑ์—… ๋ฐ ๋‚ด๊ตฌ์„ฑ์€ S3์— ๋น„ํ•ด ๋–จ์–ด์ง
  • ๋ณด์•ˆ, ์ธ์ฆ ๋กœ์ง์€ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•จ