What is Arcane#
Arcane is an open-source (BSD-3-Clause) web interface for managing Docker, positioned as a modern alternative to Portainer. The project is fairly young, though it didn’t appear yesterday either, and it’s developing quickly: the GitHub repository already has more than six thousand stars, and the feature list looks quite impressive - containers, images, volumes, networks, Docker Swarm support, templates for quick deployment, remote environments via agents, image vulnerability scanning, RBAC, and OIDC for single sign-on.
Arcane occupies a niche between Dockge, which is tied almost exclusively to docker-compose and can’t fully manage images/networks/volumes (not to mention that the project is more abandoned than alive at this point), and Portainer, which many find excessive and heavyweight for a home lab.
Thus, Arcane tries to offer a “golden mean”: an understandable, modern UI while still providing full management of everything Docker offers.
If you’ve already tried Komodo or Portainer, the key difference in Arcane’s philosophy is that it’s primarily a UI on top of the Docker Engine on a specific host (plus agents for remote hosts), rather than a full-fledged CI/CD platform with procedures and git integration like Komodo. For a home lab with one or two hosts, that can actually be simpler and easier to understand. Although of course, CI/CD pipeline capabilities are there too.
In fact, it’s a competitor to the Dockhand project, but with a significant difference. I pointed out the license Arcane is distributed under at the start of the article for a reason. Dockhand, although a direct competitor of today’s subject, is distributed under the commercial BSL 1.1 license, and some features are behind a paywall even in the free version.
What you’ll need before installing#
- A host with Docker and Docker Compose installed.
- Open port 3552 (default) or a configured reverse proxy.
- A pair of generated secrets -
ENCRYPTION_KEYandJWT_SECRET(we’ll generate these in step 2).
Step 1. Create compose.yaml#
Create a separate folder for Arcane and a compose.yaml file inside it:
services:
arcane: # Service name (used as the container name on the Docker network)
image: ghcr.io/getarcaneapp/manager:latest # Container image, tag latest - the newest version
container_name: arcane # Explicit container name (instead of an auto-generated one)
#ports:
# - '3552:3552' # Port publishing disabled - access goes only through Traefik, the port isn't opened directly externally
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Access to the host's Docker socket - needed by the app to manage containers/images
- /home/stilicho/docker/arcane/data:/app/data # Persistent app data storage on the host
environment:
- APP_URL=https://arcane.stilicho.ru # Public URL the app is accessible at
- PUID=1000 # User ID inside the container (for file permissions on the volume)
- PGID=1000 # Group ID inside the container (for file permissions on the volume)
- ENCRYPTION_KEY=xxxxxxxxxxxxxxxxxxxxxx # App data encryption key (secret)
- JWT_SECRET=xxxxxxxxxxxxxxxxxxxxxx # Secret used to sign authorization JWT tokens
- TZ=Europe/Moscow # Container timezone
cgroup: host # Container uses the host's cgroup (needed for monitoring/managing other containers' resources)
restart: unless-stopped # Auto-restart the container on crash/host reboot, except after a manual stop
networks:
proxy: # Connect to the proxy network (used by Traefik)
healthcheck:
test: ['CMD', '/app/arcane', 'health'] # Command to check the container's status
interval: 30s # Interval between checks
timeout: 5s # Maximum time to wait for a response to the check
retries: 3 # Number of failed attempts before the status becomes "unhealthy"
labels:
- "traefik.enable=true" # Enable Traefik for this container
# =========================
# HTTP ROUTER (port 80)
# =========================
- "traefik.http.routers.arcane.entrypoints=web"
# Traefik listens for incoming traffic on the "web" entrypoint (usually :80)
# this is where http://arcane.stilicho.ru lands
- "traefik.http.routers.arcane.rule=Host(`arcane.stilicho.ru`)"
# Rule: if Host matches - use this router
# Traefik compares the Host header
- "traefik.http.routers.arcane.middlewares=arcane-https-redirect"
# Apply the middleware (redirect to HTTPS)
# BEFORE proxying into the container
- "traefik.http.middlewares.arcane-https-redirect.redirectscheme.scheme=https"
# The middleware itself:
# Traefik does NOT send the request to the container
# it immediately responds to the client:
# 301 Redirect → https://arcane.stilicho.ru
# =========================
# HTTPS ROUTER (port 443)
# =========================
- "traefik.http.routers.arcane-secure.entrypoints=websecure"
# Incoming HTTPS traffic (usually port 443)
- "traefik.http.routers.arcane-secure.rule=Host(`arcane.stilicho.ru`)"
# Same domain rule
- "traefik.http.routers.arcane-secure.tls=true"
# Enable TLS:
# Traefik terminates SSL (TLS termination)
# decrypts HTTPS → works as plain HTTP from here on
- "traefik.http.routers.arcane-secure.service=arcane"
# Specify which service to send traffic to
# router → service pairing
# =========================
# SERVICE (where the traffic goes)
# =========================
- "traefik.http.services.arcane.loadbalancer.server.port=3552"
# Key line:
# Traefik takes the container's IP on the proxy network
# and makes a request to:
# http://arcane:3552 (inside the Docker network)
# NOT via localhost and NOT via ports
# =========================
# NETWORK
# =========================
- "traefik.docker.network=proxy"
# Specify which network to look for the container on
# important if the container is on multiple networks
# Traefik will take the IP specifically from the proxy network
# Top-level network definitions (available to all services)
networks:
proxy: # Name of the network the service connects to
external: true # The network already exists (created separately, e.g. for Traefik), Compose doesn't create itA few things worth noting right away:
PUID/PGID- if not specified, Arcane creates files under the built-in unprivileged user65532:65532. If you want files on the host to belong to a specific user, set their UID/GID. So GID and UID here aren’t just there to look nice in the compose file./var/run/docker.sock- a required volume, through which Arcane gets access to Docker. Below is a more secure setup using a socket proxy.arcane-data- the base compose file suggests using a Docker volume for the database and Arcane project data, but I prefer a bind mount. That’s my personal preference.
Optionally you can add two more volumes if you plan to use the corresponding features:
/builds- working directory for the Build Workspace (building images from a Dockerfile right in the interface)./backups- where Arcane stores exported volume backups. Yes, the app can back up Docker volumes, so think about which installation option suits you best.
Step 2. Generate secrets#
ENCRYPTION_KEY and JWT_SECRET must be 32-byte values (hex, base64, or raw). The easiest way to generate them is via openssl right in the terminal:
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)"
echo "JWT_SECRET=$(openssl rand -hex 32)"Substitute the resulting values into compose.yaml in place of xxxxxxxxxxxxxxxxxxxxxx.
Step 3. If you want to connect existing compose projects#
This is an important point that’s easy to miss. For Arcane to be able to manage a compose project you already have on the host (like your stacks in /opt/docker or a similar folder), the path to the project must match outside and inside the container. That is, it’s incorrect to mount it like this:
volumes:
- /opt/docker:/app/data/projectsThe correct way is to mount it one-to-one and explicitly set the environment variable:
volumes:
- /opt/docker:/opt/docker
environment:
- PROJECTS_DIRECTORY=/opt/dockerIn the video I actually didn’t do it one-to-one, but it still worked.
Then relative paths inside compose files (like ./config) will resolve the same way as if you were running docker compose by hand from that folder.
Step 4. If your host uses SELinux#
If you’re running hosts on Fedora/RHEL/CentOS with SELinux enabled, you should choose one of two options:
Option A - socket proxy (recommended). Instead of mounting docker.sock directly into Arcane, run tecnativa/docker-socket-proxy, which only exposes allowed operations (in the example below - containers, images, networks, volumes, exec, events - without access to secrets, swarm, or system operations):
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:latest
container_name: arcane-docker-proxy
privileged: true
environment:
- EVENTS=1
- PING=1
- VERSION=1
- AUTH=0
- SECRETS=0
- POST=1
- BUILD=0
- COMMIT=0
- CONFIGS=0
- CONTAINERS=1
- DISTRIBUTION=0
- EXEC=1
- IMAGES=1
- INFO=1
- NETWORKS=1
- NODES=0
- PLUGINS=0
- SERVICES=0
- SESSION=0
- SWARM=0
- SYSTEM=0
- TASKS=0
- VOLUMES=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
arcane:
image: ghcr.io/getarcaneapp/manager:latest
container_name: arcane
ports:
- '3552:3552'
volumes:
- arcane-data:/app/data
- /path/to/projects:/app/data/projects:z
environment:
- PUID=1000
- PGID=1000
- ENCRYPTION_KEY=xxxxxxxxxxxxxxxxxxxxxx
- JWT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxx
- DOCKER_HOST=tcp://docker-socket-proxy:2375
volumes:
arcane-data:This approach is worth considering even without SELinux - simply as a more secure practice, since direct access to docker.sock is effectively equivalent to root access to the host. Many people have started doing this now specifically to protect the Docker socket on Debian/Ubuntu distributions.
Option B - direct socket mounting with an SELinux label via security_opt: - label:disable and the :z suffix on the projects volume - quicker to set up, but less strict from a security standpoint.
Step 5. Launch#
docker compose up -dCheck that the container started and isn’t stuck in a restart loop:
docker compose logs -f arcaneor you can check the logs in the Dozzle app - I have an overview of it on my channel
Step 6. First login#
Open http://<host-address>:3552 in your browser. On first login, Arcane will ask you to change the default admin password - do this right away, before you expose the interface to any external network.
Step 7. Reverse proxy and WebSocket#
Arcane relies heavily on a WebSocket connection for real-time logs, metrics, and container statuses, so when publishing it through a reverse proxy, you must enable proxying of the connection upgrade. If you already have Traefik set up, it’s enough to add the labels shown above.
Traefik proxies WebSocket out of the box, no extra headers need to be configured. For Nginx or Apache you’ll need to explicitly forward the Upgrade/Connection headers - keep that in mind if you decide to publish Arcane through something other than Traefik.
Step 8. Health check (optional)#
The image has a built-in arcane health command, which is convenient to hook up as a Docker healthcheck:
healthcheck:
test: ['CMD', './arcane', 'health', '--timeout', '2s']
interval: 10s
timeout: 3s
retries: 5
start_period: 15sstart_period gives time for the database migrations to run on first start, so that time doesn’t count against failed checks.
What else Arcane can do#
Besides basic management of containers/images/networks/volumes, the interface has a few things that make the project worth a closer look, especially if you’re considering moving away from Portainer:
- Remote Environments - connect remote Docker hosts via a separate agent, similar to Periphery in Komodo, just without a git repository or procedures - just another host in the list of environments.
- Templates and template registries - quick deployment of typical stacks without hand-writing a compose file.
- Vulnerability Scans - scan images for vulnerabilities right from the interface.
- RBAC and OIDC SSO - if you already have Authentik or another OIDC provider running, Arcane can be connected to it for single sign-on, as is often done with other services in a home lab.
- Docker Swarm - basic support, if any readers are still running a Swarm cluster.
- Auto-updates - Arcane can check for and pull its own updates automatically.
Summary#
Arcane is a good option if Portainer feels excessive and Dockge feels too tightly tied to plain docker-compose without proper image and volume management (and the project isn’t really being developed anymore). On top of that, you still get features that Dockhand charges extra for. Installing with my docker compose file takes 30 seconds, and the settings really worth doing in advance are generating proper secrets, planning the path to your existing projects (PROJECTS_DIRECTORY), and, where possible, exposing access to the Docker socket via a socket proxy instead of mounting it directly.





