Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: docker

on:
pull_request:
branches: [master]
push:
branches: [master]
tags: ['v*']
workflow_dispatch:

permissions:
contents: read

concurrency:
group: docker-${{ github.ref }}
cancel-in-progress: true

env:
IMAGE: ghcr.io/shadowsocks/shadowsocks-c

jobs:
image:
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 20
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
- name: Build tested static runtime image
uses: docker/build-push-action@v7
with:
context: .
file: docker/static/Dockerfile
target: runtime
platforms: linux/${{ matrix.arch }}
load: true
tags: shadowsocks-c:test
labels: org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha,scope=docker-${{ matrix.arch }}
cache-to: type=gha,scope=docker-${{ matrix.arch }},mode=max
- name: Exercise read-only, unprivileged runtime containers
run: python3 tests/docker_smoke.py --image shadowsocks-c:test
- name: Log in to GHCR
if: github.repository == 'shadowsocks/shadowsocks-c' && github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish tested architecture image
if: github.repository == 'shadowsocks/shadowsocks-c' && github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
env:
COMMIT: ${{ github.sha }}
ARCH: ${{ matrix.arch }}
run: |
docker tag shadowsocks-c:test "$IMAGE:sha-$COMMIT-$ARCH"
docker push "$IMAGE:sha-$COMMIT-$ARCH"

publish:
needs: image
if: github.repository == 'shadowsocks/shadowsocks-c' && github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
steps:
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Image tags
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE }}
flavor: latest=false
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
type=sha,format=long
type=semver,pattern={{version}}
- name: Publish multi-platform manifest
env:
COMMIT: ${{ github.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
args=()
while IFS= read -r tag; do
if [ -n "$tag" ]; then args+=(--tag "$tag"); fi
done <<< "$TAGS"
docker buildx imagetools create "${args[@]}" \
"$IMAGE:sha-$COMMIT-amd64" "$IMAGE:sha-$COMMIT-arm64"
docker buildx imagetools inspect "$IMAGE:sha-$COMMIT"
104 changes: 79 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# shadowsocks-c

[![Build Status](https://travis-ci.com/shadowsocks/shadowsocks-libev.svg?branch=master)](https://travis-ci.com/shadowsocks/shadowsocks-libev) [![Snap Status](https://snapcraft.io/shadowsocks-libev/badge.svg)](https://snapcraft.io/shadowsocks-libev)
[![Build](https://github.com/shadowsocks/shadowsocks-c/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/shadowsocks/shadowsocks-c/actions/workflows/build.yml) [![Tests](https://github.com/shadowsocks/shadowsocks-c/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/shadowsocks/shadowsocks-c/actions/workflows/tests.yml) [![Portability](https://github.com/shadowsocks/shadowsocks-c/actions/workflows/portability.yml/badge.svg?branch=master)](https://github.com/shadowsocks/shadowsocks-c/actions/workflows/portability.yml)

## Intro

Expand All @@ -13,6 +13,41 @@ created by [@clowwindy](https://github.com/clowwindy), and maintained by

Current version: 3.3.6 | [Changelog](debian/changelog)

## Project history and rename

This repository began as **shadowsocks-libev**, the lightweight C implementation
of Shadowsocks built around the libev event loop. It later entered a bug-fix-only
maintenance phase, with new development directed toward
[shadowsocks-rust](https://github.com/shadowsocks/shadowsocks-rust).

In September 2026, the C implementation was modernized with a focus on
self-contained builds and portability. [The build modernization](https://github.com/shadowsocks/shadowsocks-c/pull/3050)
bundled pinned dependency sources, removed several external dependencies, and
added static-build validation across platforms.
[The subsequent migration](https://github.com/shadowsocks/shadowsocks-c/pull/3051)
replaced libev with libuv, added Windows IOCP and macOS kqueue support, expanded
asynchronous runtime DNS coverage, and strengthened CI lint checks.

The project and GitHub repository were renamed **shadowsocks-c** to reflect its
continuing identity as a pure C implementation. This repository retains the
shadowsocks-libev commit history and releases. The canonical repository is now
[shadowsocks/shadowsocks-c](https://github.com/shadowsocks/shadowsocks-c).

### Compatibility with shadowsocks-libev

- Commands such as `ss-local` and `ss-server`, the `shadowsocks.h` API, and the
embedding library ABI remain compatible.
- New builds provide `libshadowsocks-c` and the CMake/pkg-config package
`shadowsocks-c`. Legacy library filenames and the `shadowsocks-libev` package
lookup name remain available as compatibility aliases.
- Existing configuration paths, distribution package names, and service names
are retained. References to `shadowsocks-libev` in the installation examples
below refer to those existing integrations.

See [the modernization notes](docs/modernization.md) for build options and
platform support, and [the performance measurements](docs/performance.md) for
measured tradeoffs.

## Features

shadowsocks-c is written in pure C and depends on [libuv](https://libuv.org/). It's designed
Expand All @@ -21,35 +56,56 @@ to be a lightweight implementation of shadowsocks protocol, in order to keep the
For a full list of feature comparison between different versions of shadowsocks,
refer to the [Wiki page](https://github.com/shadowsocks/shadowsocks/wiki/Feature-Comparison-across-Different-Versions).

The project is now **shadowsocks-c**. Commands (`ss-local`, `ss-server`, etc.)
and the `shadowsocks.h` API remain compatible. New builds produce
`libshadowsocks-c` with CMake/pkg-config package `shadowsocks-c`; legacy library
filenames and package lookup names remain available as compatibility aliases.
Existing configuration paths, distro package names and service names are retained.
Repository links below still point to the current GitHub repository.

## Quick Start

Snap is the recommended way to install the latest binaries.
### Docker (recommended)

### Install snap core
Docker is the recommended way to run a server. The image contains the bundled,
fully static C binaries and supports Linux AMD64 and ARM64, including Linux
containers under Docker Desktop on macOS and Windows.

https://snapcraft.io/core
Create `config.json` and replace the example password with your own:

### Install from snapcraft.io
```json
{
"server": "0.0.0.0",
"server_port": 8388,
"password": "replace-with-a-long-random-password",
"method": "aes-256-gcm",
"mode": "tcp_and_udp"
}
```

Stable channel:
In a POSIX shell, start the server with the configuration mounted read-only:

```bash
sudo snap install shadowsocks-libev
```sh
docker pull ghcr.io/shadowsocks/shadowsocks-c:latest
docker run -d --name shadowsocks-c --restart unless-stopped \
--user "$(id -u):$(id -g)" --read-only --cap-drop=ALL \
--security-opt=no-new-privileges:true \
-p 8388:8388/tcp -p 8388:8388/udp \
--mount type=bind,src="$PWD/config.json",dst=/etc/shadowsocks-c/config.json,readonly \
ghcr.io/shadowsocks/shadowsocks-c:latest
```

Edge channel:
Using your user ID lets the container read a configuration file owned by you.
View logs with `docker logs shadowsocks-c`; stop it with `docker stop shadowsocks-c`.
Configure your Shadowsocks client with the server address, port, password and
method above.

```bash
sudo snap install shadowsocks-libev --edge
`latest` follows `master`; version tags and `sha-<full-commit>` tags identify
specific published builds. If the registry image is not yet available, build it
from this checkout with the same name, then run the command above without pulling:

```sh
docker build -f docker/static/Dockerfile --target runtime \
-t ghcr.io/shadowsocks/shadowsocks-c:latest .
```

See [Docker image details](docker/static/README.md) for publishing, updates,
client mode and build options. Existing Snap packages still use the
`shadowsocks-libev` name and may predate this modernization.

## Installation

### Distribution-specific guide
Expand Down Expand Up @@ -288,13 +344,11 @@ The historical Autotools scripts in `docker/mingw` are superseded by this build.

### Docker

As you expect, simply pull the image and run.
```
docker pull shadowsocks/shadowsocks-libev
docker run -e PASSWORD=<password> -p<server-port>:8388 -p<server-port>:8388/udp -d shadowsocks/shadowsocks-libev
```

More information about the image can be found [here](docker/alpine/README.md).
Use the [recommended Docker installation](#docker-recommended) above.
The image is `ghcr.io/shadowsocks/shadowsocks-c`; it accepts a JSON configuration
file or the normal `ss-server` arguments. The historical `PASSWORD` environment
variable wrapper belongs to the older Docker Hub image and is not used here.
See [image and build details](docker/static/README.md).

## Usage

Expand Down
7 changes: 7 additions & 0 deletions docker/static/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,12 @@ COPY --from=build /out/ /

# No libc, dynamic loader, package manager or shell in the runtime image.
FROM scratch AS runtime
LABEL org.opencontainers.image.title="shadowsocks-c" \
org.opencontainers.image.description="Self-contained Shadowsocks in C with libuv" \
org.opencontainers.image.source="https://github.com/shadowsocks/shadowsocks-c" \
org.opencontainers.image.licenses="GPL-3.0-or-later"
COPY --from=build /out/ /usr/local/
USER 65532:65532
EXPOSE 8388/tcp 8388/udp
ENTRYPOINT ["/usr/local/bin/ss-server"]
CMD ["-c", "/etc/shadowsocks-c/config.json"]
43 changes: 42 additions & 1 deletion docker/static/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,45 @@
# Fully static Linux builds
# Docker image and static Linux builds

Use `ghcr.io/shadowsocks/shadowsocks-c` for Linux AMD64 or ARM64. The
[README quick start](../../README.md#docker-recommended) shows server configuration,
TCP/UDP port mapping and a read-only configuration mount. Docker Desktop runs
these Linux images on macOS and Windows.

The default entrypoint is `ss-server -c /etc/shadowsocks-c/config.json`.
Passing arguments overrides that default configuration argument. The image runs
as UID/GID `65532:65532` unless `--user` is supplied; the mounted file must be
readable by that user. Logs go to standard output/error. No writable filesystem
is required for normal server operation.

To update a running server, pull the desired tag, remove the old container with
`docker rm -f shadowsocks-c`, and repeat the quick-start run command. The host
configuration file is preserved. `latest` tracks `master`; choose a published
version tag or `sha-<full-commit>` to pin a build.

For a SOCKS5 client, override the entrypoint with
`--entrypoint /usr/local/bin/ss-local`, supply a client configuration using `-c`,
and publish the configured local TCP/UDP port. Bind the local listener to
`0.0.0.0` inside the container and restrict its host mapping to loopback, such as
`-p 127.0.0.1:1080:1080/tcp -p 127.0.0.1:1080:1080/udp`.

## Publishing

`.github/workflows/docker.yml` builds each architecture on a native runner and
exercises the actual scratch server/client containers with concurrent TCP,
hostname resolution, UDP and clean shutdown. PRs run these checks without
publishing. Pushes to `master` publish `latest` and `sha-<full-commit>`; `v*`
version tags publish the version without the leading `v` and the commit tag.
The multi-platform tag is assembled only after both architecture tests pass.
Publishing uses the repository's `GITHUB_TOKEN` with `packages: write`.

The first image becomes available after this workflow lands on `master` and
completes. GitHub initially creates packages as private: a package administrator
must set `shadowsocks-c` to **Public** in its package settings before anonymous
pulls work. This is a one-time registry setting; see
[GitHub's container registry documentation](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#pushing-container-images).
Until then, the quick start also provides a local image build command.

## Build locally

The Alpine/musl builder uses Clang with LLD by default and links both third-party dependencies and libc statically.
`WITH_STATIC=ON` alone only selects static third-party libraries; this image also
Expand Down
Loading
Loading