Skip to content

Commit da78d34

Browse files
authored
chore: Release CLI as a docker image (#192)
* chore: Release CLI as a docker image * fix: Make the image smaller * chore: Fix CR issues, try to publish the image prerelease * chore: Try if version was released before releasing docker version * chore: Try if version was released before releasing docker version * chore: Use DOCKERHUB_TOKEN * chore: Remove unused eslint.config.js from Dockerfile
1 parent 6f7cffb commit da78d34

File tree

5 files changed

+324
-1
lines changed

5 files changed

+324
-1
lines changed

.github/workflows/release.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ jobs:
3737

3838
- name: Build the CLI
3939
run: npm run build
40+
41+
- name: Store version before release
42+
id: version-before
43+
run: echo "VERSION=$(node -p 'require("./package.json").version')" >> "$GITHUB_OUTPUT"
44+
4045
- name: Run npm release
4146
run: npm run release
4247
env:
@@ -45,3 +50,28 @@ jobs:
4550
GIT_AUTHOR_EMAIL: [email protected]
4651
GIT_COMMITTER_NAME: Tolgee Machine
4752
GIT_COMMITTER_EMAIL: [email protected]
53+
54+
- name: Extract version after release
55+
id: version-after
56+
run: echo "VERSION=$(node -p 'require("./package.json").version')" >> "$GITHUB_OUTPUT"
57+
58+
- name: Check if version was released
59+
id: version-check
60+
run: |
61+
if [ "${{ steps.version-before.outputs.VERSION }}" != "${{ steps.version-after.outputs.VERSION }}" ]; then
62+
echo "RELEASED=true" >> "$GITHUB_OUTPUT"
63+
echo "New version released: ${{ steps.version-before.outputs.VERSION }} -> ${{ steps.version-after.outputs.VERSION }}"
64+
else
65+
echo "RELEASED=false" >> "$GITHUB_OUTPUT"
66+
echo "No new version released (version remains ${{ steps.version-before.outputs.VERSION }})"
67+
fi
68+
69+
- name: Build and push Docker image
70+
if: steps.version-check.outputs.RELEASED == 'true'
71+
run: ./scripts/build-docker.sh latest linux/amd64,linux/arm64 push
72+
env:
73+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
74+
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_PASSWORD }}
75+
VERSION: ${{ steps.version-after.outputs.VERSION }}
76+
GITHUB_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
77+
BUILD_DATE: ${{ github.event.workflow_run.run_started_at || github.event.workflow_run.head_commit.timestamp }}

Dockerfile

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Multi-stage Docker build for Tolgee CLI
2+
# Stage 1: Build stage with dev dependencies
3+
FROM node:24-alpine AS builder
4+
5+
# Set working directory
6+
WORKDIR /app
7+
8+
# Copy package files first for better Docker layer caching
9+
COPY package*.json ./
10+
11+
# Install ALL dependencies (including dev dependencies needed for building)
12+
RUN npm ci && npm cache clean --force
13+
14+
# Copy source files
15+
COPY src/ ./src/
16+
COPY scripts/ ./scripts/
17+
COPY tsconfig*.json ./
18+
19+
# Copy additional files needed for build
20+
COPY textmate/ ./textmate/
21+
COPY extractor.d.ts ./
22+
COPY schema.json ./
23+
24+
# Build the CLI
25+
RUN npm run build
26+
27+
# Stage 2: Production stage with only runtime dependencies
28+
FROM node:24-alpine AS production
29+
30+
# Set working directory
31+
WORKDIR /app
32+
33+
# Copy package files first for better Docker layer caching
34+
COPY package*.json ./
35+
36+
# Install ONLY production dependencies
37+
RUN npm ci --only=production && npm cache clean --force
38+
39+
# Copy built application files from builder stage
40+
COPY --from=builder /app/dist/ ./dist/
41+
COPY --from=builder /app/textmate/ ./textmate/
42+
COPY --from=builder /app/extractor.d.ts ./
43+
COPY --from=builder /app/schema.json ./
44+
45+
# Copy documentation files
46+
COPY README.md ./
47+
COPY LICENSE ./
48+
49+
# Make the CLI binary executable
50+
RUN chmod +x ./dist/cli.js
51+
52+
# Create a non-root user for security
53+
RUN addgroup -g 1001 -S tolgee && \
54+
adduser -S tolgee -u 1001
55+
56+
# Switch to non-root user
57+
USER tolgee
58+
59+
# Set the entrypoint to the CLI binary
60+
ENTRYPOINT ["node", "./dist/cli.js"]
61+
62+
# Default command shows help
63+
CMD ["--help"]

HACKING.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ to get some work done.
55

66
## Toolchain
77

8-
To work on this project, you will just need Node 16+ (and Docker to run tests). We use `npm` to manage dependencies,
8+
To work on this project, you will just need Node 22+ (and Docker to run tests). We use `npm` to manage dependencies,
99
and [prettier](https://github.com/prettier/prettier) to lint our code.
1010

1111
## Scripts
@@ -47,6 +47,31 @@ TOLGEE_TEST_BACKEND_URL=http://localhost:8080 npm run test:e2e
4747

4848
When this environment variable is set, the Docker backend will not be started, and tests will use the specified URL instead.
4949

50+
## Building Docker Images
51+
52+
The project includes Docker support for containerized deployment. The Docker setup consists of:
53+
54+
- `Dockerfile`: Multi-stage build configuration using Node.js 22 Alpine
55+
- `scripts/build-docker.sh`: Comprehensive build script with multi-platform support
56+
57+
### Prerequisites
58+
59+
- Docker installed and running
60+
- For multi-platform builds: Docker Buildx
61+
- For pushing images: Docker Hub credentials
62+
63+
### Building Docker Images
64+
65+
The `scripts/build-docker.sh` script provides several build options:
66+
67+
**Basic build (current platform only):**
68+
69+
```bash
70+
./scripts/build-docker.sh
71+
```
72+
73+
More information about possible build options is documented in the `build-docker.sh`.
74+
5075
## Code & internals overview
5176

5277
### Command parsing

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ pnpm add --global @tolgee/cli
2323

2424
See our [documentation](https://tolgee.io/tolgee-cli/installation) for more information.
2525

26+
### Docker Installation
27+
Alternatively, you can use the Docker image:
28+
29+
```sh
30+
# Pull the latest image
31+
docker pull tolgee/cli:latest
32+
33+
# Run directly
34+
docker run --rm tolgee/cli:latest --help
35+
36+
# Create an alias for easier usage
37+
alias tolgee="docker run --rm -v \$(pwd):/workspace -w /workspace tolgee/cli:latest"
38+
```
39+
40+
The Docker images are available on [Docker Hub](https://hub.docker.com/r/tolgee/cli) and support multiple platforms (linux/amd64, linux/arm64).
41+
2642
## Usage
2743
Once installed, you'll have access to the `tolgee` command. Run `tolgee help` to see all the supported commands, their
2844
options and arguments.

scripts/build-docker.sh

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/bin/bash
2+
3+
# Build Docker image for Tolgee CLI
4+
# Usage:
5+
# ./scripts/build-docker.sh [tag] [platform] [push]
6+
#
7+
# Examples:
8+
# ./scripts/build-docker.sh # Build for current platform with default tag
9+
# ./scripts/build-docker.sh latest # Build for current platform with specific tag
10+
# ./scripts/build-docker.sh latest linux/arm64 # Build for specific single platform (available locally)
11+
# ./scripts/build-docker.sh latest linux/amd64,linux/arm64 # Multi-platform build (NOT available locally)
12+
# ./scripts/build-docker.sh latest linux/amd64,linux/arm64 push # Multi-platform build and push
13+
#
14+
# Environment variables for push:
15+
# DOCKERHUB_USERNAME - Docker Hub username
16+
# DOCKERHUB_TOKEN - Docker Hub token/password
17+
# VERSION - Version for additional tagging (optional)
18+
#
19+
# Note: Multi-platform builds are stored in buildx cache and not available locally.
20+
# To run the image locally after a multi-platform build, either:
21+
# 1. Build for your current platform only (e.g., linux/arm64 on Apple Silicon)
22+
# 2. Push the multi-platform image to a registry and pull it
23+
24+
set -euo pipefail
25+
26+
# Default values
27+
TAG=${1:-"dev"}
28+
PLATFORM=${2:-""}
29+
PUSH=${3:-""}
30+
IMAGE_NAME="tolgee/cli"
31+
32+
# Colors for output
33+
RED='\033[0;31m'
34+
GREEN='\033[0;32m'
35+
YELLOW='\033[1;33m'
36+
NC='\033[0m' # No Color
37+
38+
echo -e "${GREEN}Building Tolgee CLI Docker image...${NC}"
39+
echo "Tag: ${TAG}"
40+
echo "Image: ${IMAGE_NAME}:${TAG}"
41+
42+
# Check if Docker is running
43+
if ! docker info > /dev/null 2>&1; then
44+
echo -e "${RED}Error: Docker is not running. Please start Docker and try again.${NC}"
45+
exit 1
46+
fi
47+
48+
# Docker login if push is requested
49+
if [ "$PUSH" = "push" ]; then
50+
if [ -z "${DOCKERHUB_USERNAME:-}" ] || [ -z "${DOCKERHUB_TOKEN:-}" ]; then
51+
echo -e "${RED}Error: DOCKERHUB_USERNAME and DOCKERHUB_TOKEN environment variables are required for push.${NC}"
52+
exit 1
53+
fi
54+
echo -e "${GREEN}Logging in to Docker Hub...${NC}"
55+
echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
56+
fi
57+
58+
# Ensure we have the built files
59+
if [ ! -d "dist" ]; then
60+
echo -e "${YELLOW}Warning: dist directory not found. Building the CLI first...${NC}"
61+
npm run build
62+
fi
63+
64+
# Prepare tags
65+
TAGS=( -t "${IMAGE_NAME}:${TAG}" )
66+
if [ -n "${VERSION:-}" ] && [ "$TAG" = "latest" ]; then
67+
TAGS+=( -t "${IMAGE_NAME}:${VERSION}" )
68+
fi
69+
70+
# Prepare labels
71+
LABELS=()
72+
if [ "$PUSH" = "push" ]; then
73+
LABELS+=( --label "org.opencontainers.image.title=Tolgee CLI" )
74+
LABELS+=( --label "org.opencontainers.image.description=A tool to interact with the Tolgee Platform through CLI" )
75+
LABELS+=( --label "org.opencontainers.image.url=https://github.com/tolgee/tolgee-cli" )
76+
LABELS+=( --label "org.opencontainers.image.source=https://github.com/tolgee/tolgee-cli" )
77+
LABELS+=( --label "org.opencontainers.image.licenses=MIT" )
78+
if [ -n "${VERSION:-}" ]; then
79+
LABELS+=( --label "org.opencontainers.image.version=${VERSION}" )
80+
fi
81+
if [ -n "${GITHUB_SHA:-}" ]; then
82+
LABELS+=( --label "org.opencontainers.image.revision=${GITHUB_SHA}" )
83+
fi
84+
if [ -n "${BUILD_DATE:-}" ]; then
85+
LABELS+=( --label "org.opencontainers.image.created=${BUILD_DATE}" )
86+
fi
87+
fi
88+
89+
# Build command
90+
BUILD_CMD=( docker build )
91+
BUILD_CMD+=( "${TAGS[@]}" )
92+
if [ ${#LABELS[@]} -gt 0 ]; then
93+
BUILD_CMD+=( "${LABELS[@]}" )
94+
fi
95+
96+
if [ -n "$PLATFORM" ]; then
97+
echo "Platform(s): ${PLATFORM}"
98+
# For multi-platform builds, we need buildx
99+
BUILD_CMD=( docker buildx build --platform "${PLATFORM}" )
100+
BUILD_CMD+=( "${TAGS[@]}" )
101+
if [ ${#LABELS[@]} -gt 0 ]; then
102+
BUILD_CMD+=( "${LABELS[@]}" )
103+
fi
104+
105+
# Check if buildx is available
106+
if ! docker buildx version > /dev/null 2>&1; then
107+
echo -e "${RED}Error: Docker buildx is required for multi-platform builds.${NC}"
108+
echo "Please install Docker buildx or build for single platform."
109+
exit 1
110+
fi
111+
112+
# Create builder if it doesn't exist
113+
if ! docker buildx inspect tolgee-builder > /dev/null 2>&1; then
114+
echo -e "${YELLOW}Creating multi-platform builder...${NC}"
115+
docker buildx create --name tolgee-builder --use
116+
else
117+
docker buildx use tolgee-builder
118+
fi
119+
120+
# Check if this is a single platform build that can be loaded locally
121+
PLATFORM_COUNT=$(echo "$PLATFORM" | tr ',' '\n' | wc -l)
122+
if [ "$PLATFORM_COUNT" -eq 1 ] && [ "$PUSH" != "push" ]; then
123+
# Single platform - we can load it to local Docker daemon
124+
BUILD_CMD+=( --load )
125+
echo -e "${GREEN}Single platform build - image will be available locally after build.${NC}"
126+
elif [ "$PUSH" = "push" ]; then
127+
# Push mode - add push flag
128+
BUILD_CMD+=( --push )
129+
echo -e "${GREEN}Build and push mode enabled.${NC}"
130+
else
131+
# Multi-platform build - cannot load to local Docker daemon
132+
echo -e "${YELLOW}Multi-platform build - image will NOT be available locally.${NC}"
133+
echo -e "${YELLOW}To run locally, build for your current platform only or push to a registry.${NC}"
134+
fi
135+
elif [ "$PUSH" = "push" ]; then
136+
# Regular build with push - we need to build and then push
137+
echo -e "${GREEN}Build and push mode enabled for single architecture.${NC}"
138+
fi
139+
140+
BUILD_CMD+=( . )
141+
142+
echo -e "${GREEN}Running:${NC} ${BUILD_CMD[*]}"
143+
"${BUILD_CMD[@]}"
144+
145+
if [ "$PUSH" = "push" ] && [ -z "$PLATFORM" ]; then
146+
# For regular builds, we need to push separately
147+
echo -e "${GREEN}Pushing images to registry...${NC}"
148+
docker push ${IMAGE_NAME}:${TAG}
149+
if [ -n "${VERSION:-}" ] && [ "$TAG" = "latest" ]; then
150+
docker push ${IMAGE_NAME}:${VERSION}
151+
fi
152+
fi
153+
154+
if [ "$PUSH" = "push" ]; then
155+
echo -e "${GREEN}✓ Docker image built and pushed successfully!${NC}"
156+
else
157+
echo -e "${GREEN}✓ Docker image built successfully!${NC}"
158+
fi
159+
160+
# Show appropriate run instruction based on build type
161+
if [ -n "$PLATFORM" ]; then
162+
PLATFORM_COUNT=$(echo "$PLATFORM" | tr ',' '\n' | wc -l)
163+
if [ "$PLATFORM_COUNT" -eq 1 ]; then
164+
# Single platform - image is available locally
165+
echo -e "${GREEN}Run with: docker run --rm ${IMAGE_NAME}:${TAG}${NC}"
166+
else
167+
# Multi-platform - image is NOT available locally
168+
echo -e "${YELLOW}Multi-platform build completed. Image is not available locally.${NC}"
169+
echo -e "${YELLOW}To run locally: build for your platform only or pull from registry after push.${NC}"
170+
fi
171+
else
172+
# Default build for current platform - always available locally
173+
echo -e "${GREEN}Run with: docker run --rm ${IMAGE_NAME}:${TAG}${NC}"
174+
fi
175+
176+
# Show image info
177+
echo -e "\n${YELLOW}Image information:${NC}"
178+
if [ -n "$PLATFORM" ]; then
179+
PLATFORM_COUNT=$(echo "$PLATFORM" | tr ',' '\n' | wc -l)
180+
if [ "$PLATFORM_COUNT" -eq 1 ]; then
181+
echo "Single-platform image built and loaded locally."
182+
docker images ${IMAGE_NAME}:${TAG}
183+
else
184+
echo "Multi-platform image built. Use 'docker buildx imagetools inspect ${IMAGE_NAME}:${TAG}' to see details."
185+
echo "Note: Multi-platform images are not available locally. Build for your current platform to run locally."
186+
fi
187+
else
188+
docker images ${IMAGE_NAME}:${TAG}
189+
fi

0 commit comments

Comments
 (0)