앞서 만들어논 포테이너를 사용할 예정이다.
name: Build and Push Docker Image
on:
push:
branches:
- dev
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=raw,value=dev
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
DBHOST=${{ secrets.DBHOST }}
DBPORT=${{ secrets.DBPORT }}
DB=${{ secrets.DB }}
USERNAME=${{ secrets.USERNAME }}
DBPASSWORD=${{ secrets.PASSWORD }}
JWTSECRET=${{ secrets.SECRET }}
JWTISSUER=${{ secrets.ISSUER }}
docker-build.yml
FROM openjdk:17-jdk-slim
WORKDIR /app
ARG DBHOST
ARG DBPORT
ARG DB
ARG USERNAME
ARG DBPASSWORD
ARG JWTSECRET
ARG JWTISSUER
ENV DBHOST=${DBHOST}
ENV DBPORT=${DBPORT}
ENV DB=${DB}
ENV USERNAME=${USERNAME}
ENV DBPASSWORD=${DBPASSWORD}
ENV JWTSECRET=${JWTSECRET}
ENV JWTISSUER=${JWTISSUER}
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY src src
RUN chmod +x ./gradlew
RUN ./gradlew clean bootJar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "build/libs/board-api.jar"]
Dockerfile
람다에서 한거랑 비슷하게 dev브런치면 실행하고 환경변수를 쓰는데 PASSWORD, SECRET같은거는 중복이 날수 있다고 경고 뜨더라
아무튼 개발은 태그 dev로 고정해놓고 GitHub Container Registry에 저장된다. 하고보니까 code페이지에 packages에서 확인하더라 아무튼 이미지 생성된건 확인했고 로컬에 풀받아서 실행해보니 실행도 확인됬다.
포테이너 웹훅 할라그랫는데 무료가 안되나보다 깃 훅으로 되려나
---
portainer api가 있어서 그걸로 진행
포테이너 로그인하고 우측상단 계정에 my account에서

토큰생성 토큰은 만든직후 아니면 다시 확인 안되는거같다 잘 저장하자
git secrets에

변수 추가하고
portainer_env_no은
portainer url/api/endpoints 치면 나오는 id값인데

이거 리스트라더라 원하는 환경 id입력하자
- name: Get existing container ID
id: get_container
run: |
CONTAINER_NAME="${{ github.event.repository.name }}"
echo "Looking for container: $CONTAINER_NAME"
# API 호출 및 JSON 파싱
RESPONSE=$(curl -s "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/json?all=true" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}")
# JSON 유효성 검사
if echo "$RESPONSE" | jq empty 2>/dev/null; then
echo "✅ Valid JSON response"
# 컨테이너 ID 추출 (안전한 방법)
CONTAINER_ID=$(echo "$RESPONSE" | jq -r '
.[] |
select(.Names[]? | test("^/'$CONTAINER_NAME'$")) |
.Id // empty
' 2>/dev/null)
# 결과 확인
if [ -z "$CONTAINER_ID" ] || [ "$CONTAINER_ID" = "null" ]; then
echo "No existing container found with name: $CONTAINER_NAME"
CONTAINER_ID=""
else
echo "Found container ID: $CONTAINER_ID"
fi
else
echo "❌ Invalid JSON or API error"
echo "Response: $RESPONSE"
CONTAINER_ID=""
fi
echo "container_id=$CONTAINER_ID" >> $GITHUB_OUTPUT
echo "container_name=$CONTAINER_NAME" >> $GITHUB_OUTPUT
- name: Delete existing container
if: steps.get_container.outputs.container_id != '' && steps.get_container.outputs.container_id != 'null'
run: |
CONTAINER_ID="${{ steps.get_container.outputs.container_id }}"
echo "Deleting container ID: $CONTAINER_ID"
# 안전한 curl 명령어
RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" \
-X DELETE "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/$CONTAINER_ID?force=true" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}")
HTTP_STATUS=$(echo $RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
BODY=$(echo $RESPONSE | sed -e 's/HTTPSTATUS:.*//g')
echo "HTTP Status: $HTTP_STATUS"
echo "Response: $BODY"
if [ $HTTP_STATUS -eq 204 ] || [ $HTTP_STATUS -eq 200 ]; then
echo "✅ Container deleted successfully"
else
echo "❌ Failed to delete container (Status: $HTTP_STATUS)"
# 컨테이너가 이미 없을 수도 있으니 계속 진행
fi
- name: Pull latest image (with error handling)
run: |
echo "Pulling latest image..."
REGISTRY_ENCODED=$(echo "${{ env.REGISTRY }}" | sed 's|/|%2F|g')
IMAGE_ENCODED=$(echo "${{ env.IMAGE_NAME }}" | sed 's|/|%2F|g')
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/images/create?fromImage=${REGISTRY_ENCODED}%2F${IMAGE_ENCODED}&tag=dev" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}" \
-H "Referer: ${{ secrets.PORTAINERURL }}"
- name: Create new container
run: |
echo "Creating new container with image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev"
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/create?name=${{ steps.get_container.outputs.container_name }}" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}" \
-H "Content-Type: application/json" \
-d "{
\"Image\": \"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev\",
\"HostConfig\": {
\"NetworkMode\": \"local\",
\"PortBindings\": {
\"8081/tcp\": [{\"HostPort\": \"8081\"}]
},
\"RestartPolicy\": {
\"Name\": \"unless-stopped\"
}
}
}"
- name: Start container
run: |
echo "Starting container..."
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/${{ steps.get_container.outputs.container_name }}/start" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}"
echo "✅ Deployment completed!"
docker-build.yml
에 내용 추가
많은 에러가 있었으나 대부분 오타들로 인한거였고
이외에는
- name: Pull latest image (with error handling)
run: |
echo "Pulling latest image..."
REGISTRY_ENCODED=$(echo "${{ env.REGISTRY }}" | sed 's|/|%2F|g')
IMAGE_ENCODED=$(echo "${{ env.IMAGE_NAME }}" | sed 's|/|%2F|g')
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/images/create?fromImage=${REGISTRY_ENCODED}%2F${IMAGE_ENCODED}&tag=dev" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}" \
-H "Referer: ${{ secrets.PORTAINERURL }}"
이게 원래 이미지 이름을 json으로 데이터로 보냈는데 계속 실패했는데 파라미터로 담아서 보내니 바로 성공했다

컨테이너가 잘 생성됬고
바로바로 proxy도 등록하고앞서 만들어논 포테이너를 사용할 예정이다.
name: Build and Push Docker Image
on:
push:
branches:
- dev
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=raw,value=dev
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
DBHOST=${{ secrets.DBHOST }}
DBPORT=${{ secrets.DBPORT }}
DB=${{ secrets.DB }}
USERNAME=${{ secrets.USERNAME }}
DBPASSWORD=${{ secrets.PASSWORD }}
JWTSECRET=${{ secrets.SECRET }}
JWTISSUER=${{ secrets.ISSUER }}
docker-build.yml
FROM openjdk:17-jdk-slim
WORKDIR /app
ARG DBHOST
ARG DBPORT
ARG DB
ARG USERNAME
ARG DBPASSWORD
ARG JWTSECRET
ARG JWTISSUER
ENV DBHOST=${DBHOST}
ENV DBPORT=${DBPORT}
ENV DB=${DB}
ENV USERNAME=${USERNAME}
ENV DBPASSWORD=${DBPASSWORD}
ENV JWTSECRET=${JWTSECRET}
ENV JWTISSUER=${JWTISSUER}
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY src src
RUN chmod +x ./gradlew
RUN ./gradlew clean bootJar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "build/libs/board-api.jar"]
Dockerfile
람다에서 한거랑 비슷하게 dev브런치면 실행하고 환경변수를 쓰는데 PASSWORD, SECRET같은거는 중복이 날수 있다고 경고 뜨더라
아무튼 개발은 태그 dev로 고정해놓고 GitHub Container Registry에 저장된다. 하고보니까 code페이지에 packages에서 확인하더라 아무튼 이미지 생성된건 확인했고 로컬에 풀받아서 실행해보니 실행도 확인됬다.
포테이너 웹훅 할라그랫는데 무료가 안되나보다 깃 훅으로 되려나
---
portainer api가 있어서 그걸로 진행
포테이너 로그인하고 우측상단 계정에 my account에서

토큰생성 토큰은 만든직후 아니면 다시 확인 안되는거같다 잘 저장하자
git secrets에

변수 추가하고
portainer_env_no은
portainer url/api/endpoints 치면 나오는 id값인데

이거 리스트라더라 원하는 환경 id입력하자
- name: Get existing container ID
id: get_container
run: |
CONTAINER_NAME="${{ github.event.repository.name }}"
echo "Looking for container: $CONTAINER_NAME"
# API 호출 및 JSON 파싱
RESPONSE=$(curl -s "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/json?all=true" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}")
# JSON 유효성 검사
if echo "$RESPONSE" | jq empty 2>/dev/null; then
echo "✅ Valid JSON response"
# 컨테이너 ID 추출 (안전한 방법)
CONTAINER_ID=$(echo "$RESPONSE" | jq -r '
.[] |
select(.Names[]? | test("^/'$CONTAINER_NAME'$")) |
.Id // empty
' 2>/dev/null)
# 결과 확인
if [ -z "$CONTAINER_ID" ] || [ "$CONTAINER_ID" = "null" ]; then
echo "No existing container found with name: $CONTAINER_NAME"
CONTAINER_ID=""
else
echo "Found container ID: $CONTAINER_ID"
fi
else
echo "❌ Invalid JSON or API error"
echo "Response: $RESPONSE"
CONTAINER_ID=""
fi
echo "container_id=$CONTAINER_ID" >> $GITHUB_OUTPUT
echo "container_name=$CONTAINER_NAME" >> $GITHUB_OUTPUT
- name: Delete existing container
if: steps.get_container.outputs.container_id != '' && steps.get_container.outputs.container_id != 'null'
run: |
CONTAINER_ID="${{ steps.get_container.outputs.container_id }}"
echo "Deleting container ID: $CONTAINER_ID"
# 안전한 curl 명령어
RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" \
-X DELETE "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/$CONTAINER_ID?force=true" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}")
HTTP_STATUS=$(echo $RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
BODY=$(echo $RESPONSE | sed -e 's/HTTPSTATUS:.*//g')
echo "HTTP Status: $HTTP_STATUS"
echo "Response: $BODY"
if [ $HTTP_STATUS -eq 204 ] || [ $HTTP_STATUS -eq 200 ]; then
echo "✅ Container deleted successfully"
else
echo "❌ Failed to delete container (Status: $HTTP_STATUS)"
# 컨테이너가 이미 없을 수도 있으니 계속 진행
fi
- name: Pull latest image (with error handling)
run: |
echo "Pulling latest image..."
REGISTRY_ENCODED=$(echo "${{ env.REGISTRY }}" | sed 's|/|%2F|g')
IMAGE_ENCODED=$(echo "${{ env.IMAGE_NAME }}" | sed 's|/|%2F|g')
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/images/create?fromImage=${REGISTRY_ENCODED}%2F${IMAGE_ENCODED}&tag=dev" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}" \
-H "Referer: ${{ secrets.PORTAINERURL }}"
- name: Create new container
run: |
echo "Creating new container with image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev"
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/create?name=${{ steps.get_container.outputs.container_name }}" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}" \
-H "Content-Type: application/json" \
-d "{
\"Image\": \"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev\",
\"HostConfig\": {
\"NetworkMode\": \"local\",
\"PortBindings\": {
\"8081/tcp\": [{\"HostPort\": \"8081\"}]
},
\"RestartPolicy\": {
\"Name\": \"unless-stopped\"
}
}
}"
- name: Start container
run: |
echo "Starting container..."
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/${{ steps.get_container.outputs.container_name }}/start" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}"
echo "✅ Deployment completed!"
docker-build.yml
에 내용 추가
많은 에러가 있었으나 대부분 오타들로 인한거였고
이외에는
- name: Pull latest image (with error handling)
run: |
echo "Pulling latest image..."
REGISTRY_ENCODED=$(echo "${{ env.REGISTRY }}" | sed 's|/|%2F|g')
IMAGE_ENCODED=$(echo "${{ env.IMAGE_NAME }}" | sed 's|/|%2F|g')
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/images/create?fromImage=${REGISTRY_ENCODED}%2F${IMAGE_ENCODED}&tag=dev" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}" \
-H "Referer: ${{ secrets.PORTAINERURL }}"
이게 원래 이미지 이름을 json으로 데이터로 보냈는데 계속 실패했는데 파라미터로 담아서 보내니 바로 성공했다

컨테이너가 잘 생성됬고
바로바로 proxy도 등록하고 route53에서 레코드도 등록


잘된다.
하다보니 web쪽에 Axios.interceptor에서 갱신시 응답이 잘 안오는걸 확인해서 수정했다.
export const init = async () => {
//console.log("===> Axios.interceptor::init()")
axios.interceptors.request.use((config) => {
const accessToken = getAccessToken();
if (accessToken) {
config.headers['Authorization'] = `${accessToken}`;
}
config.headers['Channel'] = 'WEB';
config.headers['X-Requested-With'] = 'XMLHttpRequest';
config.headers['Access-Control-Allow-Credentials'] = true;
config.headers['Access-Control-Allow-Origin'] = '*';
config.headers['Access-Control-Allow-Methods'] = 'GET, PUT, POST, DELETE, OPTIONS';
config.headers['Cache-Control'] = 'no-cache';
config.headers['Pragma'] = 'no-cache';
console.log('[ Axios.interceptor ] :: processAwait ' + processAwait);
return config;
}, err => {
console.error(err);
Promise.reject(err);
});
axios.interceptors.response.use(
response => {
//console.log('[ Axios.interceptor ] :: response OK');
return response;
},async err => {
const config = err.config; // 기존에 수행하려고 했던 작업
const response = err.response;
console.log(`[ Axios.interceptor ] :: response [${config.method}] ${config.url}`);
if( response.status === 401)
{
console.log('[ Axios.interceptor ] :: ERR :: [401]');
if( response.data.object === -1001) {
if (processAwait == null) {
processAwait = 'temp'
let refreshTokenParams = null;
let config = response.config;
let headers = config.headers;
headers['Authorization'] = `${getRefreshToken()}`;
refreshTokenParams = {refreshToken: getRefreshToken()};
try {
const authBaseURL = import.meta.env.VITE_AUTH_URL;
processAwait = axios.create({
baseURL: authBaseURL,
headers
}).post("/refresh", refreshTokenParams);
const result = await processAwait;
const {status, data} = result;
if (status == 200) {
console.log('[ Axios.interceptor ] :: ERR :: [/refresh] : GET NEW TOKEN');
// 토큰 세팅
const newAccessToken = data.token;
const newRefreshToken = data.refreshToken;
setAccessToken(newAccessToken);
setRefreshToken(newRefreshToken);
console.log('[ Axios.interceptor ] :: ERR :: [/refresh] : 엑세스 토큰 갱신');
config.headers.Authorization = `Bearer ${getAccessToken()}`;
processAwait = null;
return axios(config);
} else {
//토큰재발급 애러 : 토큰 삭제 후 로그인 페이지
processAwait = null;
removeAuthTokens()
location.href = '/login';
return Promise.reject(result);
}
} catch (e) {
//토큰재발급 애러 : 토큰 삭제 후 로그인 페이지
removeAuthTokens()
location.href = '/login';
return Promise.reject(e)
}
} else {
await processAwait
let config = response.config;
let headers = config.headers;
config.headers['Authorization'] = getAccessToken();
const retryAxios = axios.create({headers});
if (config.method === 'get') {
return retryAxios.get(config.url)
} else if (config.method === 'post') {
return retryAxios.post(config.url, config.data);
} else if (config.method === 'patch') {
return retryAxios.patch(config.url, config.data);
} else if (config.method === 'put') {
return retryAxios.put(config.url, config.data);
} else if (config.method === 'delete') {
return retryAxios.delete(config.url, {data: config.data});
}
}
}
else if(response.data.object === -1002)
{
console.log('[ Axios.interceptor ] :: ERR :: [-1002] : WRONG TOKEN -> go login');
removeAuthTokens();
location.href = '/';
}
else
{
console.log('else')
return Promise.resolve(response);
}
}
else if(response.status === 500)
{
return Promise.resolve(response);
}
});
}
await processAwait.then(({status, data})
써가지고 갱신응답이 전달이 안되더라
프론트도 배포하자
FROM node:22-alpine AS builder
WORKDIR /app
ARG VITE_SERVER_URL
ARG VITE_AUTH_URL
ARG VITE_APP_ENV
ARG VITE_APP_COOKIE_DOMAIN
ENV VITE_SERVER_URL=$VITE_SERVER_URL
ENV VITE_AUTH_URL=$VITE_AUTH_URL
ENV VITE_APP_ENV=$VITE_APP_ENV
ENV VITE_APP_COOKIE_DOMAIN=${VITE_APP_COOKIE_DOMAIN}
# 패키지 파일 복사 및 설치
COPY package.json package-lock.json ./
RUN npm ci
# 소스코드 복사 및 빌드
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
# 빌드된 파일을 nginx로 복사
COPY --from=builder /app/dist /usr/share/nginx/html
# Nginx 설정 파일 복사
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Dockerfile
name: Build and Push Docker Image
on:
push:
branches:
- dev
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=raw,value=latest
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VITE_SERVER_URL=${{ secrets.VITE_SERVER_URL }}
VITE_AUTH_URL=${{ secrets.VITE_AUTH_URL }}
VITE_APP_ENV=${{ secrets.VITE_APP_ENV }}
VITE_APP_COOKIE_DOMAIN=board-web.dev.atteri.co.kr
- name: Get existing container ID
id: get_container
run: |
CONTAINER_NAME="${{ github.event.repository.name }}"
echo "Looking for container: $CONTAINER_NAME"
# API 호출 및 JSON 파싱
RESPONSE=$(curl -s "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/json?all=true" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}")
# JSON 유효성 검사
if echo "$RESPONSE" | jq empty 2>/dev/null; then
echo "✅ Valid JSON response"
# 컨테이너 ID 추출 (안전한 방법)
CONTAINER_ID=$(echo "$RESPONSE" | jq -r '
.[] |
select(.Names[]? | test("^/'$CONTAINER_NAME'$")) |
.Id // empty
' 2>/dev/null)
# 결과 확인
if [ -z "$CONTAINER_ID" ] || [ "$CONTAINER_ID" = "null" ]; then
echo "No existing container found with name: $CONTAINER_NAME"
CONTAINER_ID=""
else
echo "Found container ID: $CONTAINER_ID"
fi
else
echo "❌ Invalid JSON or API error"
echo "Response: $RESPONSE"
CONTAINER_ID=""
fi
echo "container_id=$CONTAINER_ID" >> $GITHUB_OUTPUT
echo "container_name=$CONTAINER_NAME" >> $GITHUB_OUTPUT
- name: Delete existing container
if: steps.get_container.outputs.container_id != '' && steps.get_container.outputs.container_id != 'null'
run: |
CONTAINER_ID="${{ steps.get_container.outputs.container_id }}"
echo "Deleting container ID: $CONTAINER_ID"
# 안전한 curl 명령어
RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" \
-X DELETE "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/$CONTAINER_ID?force=true" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}")
HTTP_STATUS=$(echo $RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
BODY=$(echo $RESPONSE | sed -e 's/HTTPSTATUS:.*//g')
echo "HTTP Status: $HTTP_STATUS"
echo "Response: $BODY"
if [ $HTTP_STATUS -eq 204 ] || [ $HTTP_STATUS -eq 200 ]; then
echo "✅ Container deleted successfully"
else
echo "❌ Failed to delete container (Status: $HTTP_STATUS)"
# 컨테이너가 이미 없을 수도 있으니 계속 진행
fi
- name: Pull latest image (with error handling)
run: |
echo "Pulling latest image..."
REGISTRY_ENCODED=$(echo "${{ env.REGISTRY }}" | sed 's|/|%2F|g')
IMAGE_ENCODED=$(echo "${{ env.IMAGE_NAME }}" | sed 's|/|%2F|g')
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/images/create?fromImage=${REGISTRY_ENCODED}%2F${IMAGE_ENCODED}&tag=latest" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}" \
-H "Referer: ${{ secrets.PORTAINERURL }}"
- name: Create new container
run: |
echo "Creating new container with image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/create?name=${{ steps.get_container.outputs.container_name }}" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}" \
-H "Content-Type: application/json" \
-d "{
\"Image\": \"${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\",
\"HostConfig\": {
\"NetworkMode\": \"local\",
\"PortBindings\": {
\"80/tcp\": [{\"HostPort\": \"3000\"}]
},
\"RestartPolicy\": {
\"Name\": \"unless-stopped\"
}
},
\"Env\": [
\"NODE_ENV=development\",
\"CHOKIDAR_USEPOLLING=true\"
]
}"
- name: Start container
run: |
echo "Starting container..."
curl -s -X POST "${{ secrets.PORTAINERURL }}/api/endpoints/${{ secrets.PORTAINER_ENV_NO }}/docker/containers/${{ steps.get_container.outputs.container_name }}/start" \
-H "X-API-KEY: ${{ secrets.PORTAINERTOKEN }}"
echo "✅ Deployment completed!"
깃액션
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# SPA 라우팅을 위한 설정
location / {
try_files $uri $uri/ /index.html;
}
# 정적 파일 캐싱
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
nginx.config

nginx proxy에서 80설정해놔서 외부접근시에는 3000으로 하고
당연히 프록시에도 3000으로 해야되는줄 알았는데 안되서 찾아보니
같은 네트워크 네부라서 80으로 바로 연결되서 3000이 아닌 80으로 해야되더라
- React 앱은 nginx로 빌드되어 컨테이너 내부에서 80번 포트로 실행
- 호스트 포트 매핑으로 외부에서는 3000번으로 접근
- 프록시는 내부 네트워크에서 80번으로 직접 접근
일단 이렇게 내부 공유용 개발배포를 진행했다.
운영의 경우 Amazon Elastic Beanstalk로 해보려고한다.
'연습' 카테고리의 다른 글
| Elastic Beanstalk & CloudFront (5) | 2025.08.22 |
|---|---|
| 백+프론트 연결 (0) | 2025.08.19 |
| api서버 & 람다 추가 (1) | 2025.08.19 |
| 프론트 (0) | 2025.08.16 |
| 람다 3 (3) | 2025.08.15 |