├── restart-server.sh ├── stop-server.sh ├── start-server.sh ├── Dockerfile ├── interact.sh ├── entrypoint.sh ├── run-command.sh └── README.md /restart-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./stop-server.sh 4 | ./start-server.sh -------------------------------------------------------------------------------- /stop-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LOG_FILE="factorio-server.log" 4 | FACTORIO_PID_FILE="factorio-server.pid" 5 | 6 | echo "Stopping Factorio Server..." | tee ${LOG_FILE} 7 | 8 | kill $(cat ${FACTORIO_PID_FILE}) > /dev/null 2>&1 9 | rm ${FACTORIO_PID_FILE} > /dev/null 2>&1 10 | sleep 4 11 | 12 | echo "Done!" 13 | -------------------------------------------------------------------------------- /start-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FIFO_NAME="factorio-server-fifo" 4 | LOG_FILE="factorio-server.log" 5 | FACTORIO_PID_FILE="factorio-server.pid" 6 | 7 | echo "Starting Factorio Server..." 8 | 9 | # Start the server and track the process ID 10 | rm ${FACTORIO_PID_FILE} > /dev/null 2>&1 11 | ./factorio/bin/x64/factorio --start-server $(cat last-game.txt) > ${LOG_FILE} 2>&1 < ${FIFO_NAME} & 12 | echo $! > ${FACTORIO_PID_FILE} 13 | 14 | echo "Done!" 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM --platform=linux/amd64 ubuntu:latest 3 | LABEL MAINTAINER='David Anderson "david@misplaced.us"' 4 | RUN apt-get update -y -qq \ 5 | && apt-get install -y -qq wget xz-utils 6 | COPY . /app 7 | WORKDIR /app 8 | RUN wget https://factorio.com/get-download/stable/headless/linux64 -q -O factorio_headless.tar.xz && \ 9 | tar xvf factorio_headless.tar.xz && \ 10 | rm factorio_headless.tar.xz 11 | ENTRYPOINT ["./entrypoint.sh"] 12 | EXPOSE 34197/udp 13 | -------------------------------------------------------------------------------- /interact.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Simple interaction script for factorio's server process 4 | # Summary: 5 | # - Redirects keystrokes to the fifo that the factorio server is listening to in real time 6 | # - Prints the server process output in real time 7 | # - Cleans up stale processes before exiting 8 | 9 | FIFO_NAME=factorio-server-fifo 10 | LOG_FILE="factorio-server.log" 11 | 12 | cat > ${FIFO_NAME} <&0 2> /dev/null & 13 | stdin_cat_pid=$! 14 | 15 | tail -f ${LOG_FILE} 16 | kill ${stdin_cat_pid} > /dev/null 2>&1 17 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Entrypoint for docker-factorio 4 | # Summary: 5 | # - Creates a fifo to interact with the `factorio` process' stdin for server commands 6 | # - Checks if there's an existing game and creates a new one if not 7 | # - Starts the server, using the fifo for stdin, and `tee`s stdout and stderr to a logfile 8 | # - Uses `interact.sh` to pass keystrokes to the server process, while printing its output 9 | # - Cleans up stale processes before exiting 10 | 11 | FIFO_NAME="factorio-server-fifo" 12 | LOG_FILE="factorio-server.log" 13 | FACTORIO_PID_FILE="factorio-server.pid" 14 | 15 | # Create fifo for interaction with the factorio server process 16 | rm ${FIFO_NAME} > /dev/null 2>&1 17 | mkfifo ${FIFO_NAME} 18 | 19 | # Check if there's an existing game - create one if not 20 | if [ ! -f "$(cat last-game.txt 2> /dev/null)" ]; then 21 | SAVE_FILE="factorio-save-$(date +"%Y_%m_%d_%I_%M_%p").zip" 22 | ./factorio/bin/x64/factorio --create ./saves/${SAVE_FILE} 2>&1 | tee ${LOG_FILE} 23 | echo "./saves/${SAVE_FILE}" > last-game.txt 24 | fi 25 | 26 | # Start the server and track the process ID 27 | rm ${FACTORIO_PID_FILE} > /dev/null 2>&1 28 | ./factorio/bin/x64/factorio --start-server $(cat last-game.txt) > ${LOG_FILE} 2>&1 < ${FIFO_NAME} & 29 | echo $! > ${FACTORIO_PID_FILE} 30 | 31 | # Kick off the server and pass keystrokes to the server process' stdin 32 | ./interact.sh 33 | kill $(cat ${FACTORIO_PID_FILE} > /dev/null 2>&1 34 | -------------------------------------------------------------------------------- /run-command.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Commandline tool to send commands and return results from a running factorio server via fifo 3 | 4 | # LICENSE NOTICE 5 | # The methodology and some of the code in this file has been copied and adapted from 6 | # the `factorio-init` project located here: 7 | # https://github.com/Bisa/factorio-init/blob/4cafc0d23aa0d0fdbd99a6bf5851908258006372/factorio 8 | 9 | # The code in the `factorio-init` project is protected by The MIT License (MIT) 10 | # The original author is: Tobias Wallin - https://github.com/Bisa 11 | 12 | # The MIT license requires that redistributed code retains its original license notice. 13 | # Below is a copy of the original license notice located at: 14 | # https://github.com/Bisa/factorio-init/blob/b476d9e6fd69b3b3a5a59e2968631f065e2fa691/LICENSE 15 | # Code outside of this file is original and not adapted from any other projects. 16 | 17 | # This code is used with appreciation and admiration. 18 | 19 | <<'###-ORIGINAL-LICENSE-NOTICE-###' 20 | The MIT License (MIT) 21 | 22 | Copyright (c) 2015 Tobias Wallin - https://github.com/Bisa 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy 25 | of this software and associated documentation files (the "Software"), to deal 26 | in the Software without restriction, including without limitation the rights 27 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | copies of the Software, and to permit persons to whom the Software is 29 | furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in all 32 | copies or substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 40 | SOFTWARE. 41 | ###-ORIGINAL-LICENSE-NOTICE-### 42 | 43 | # Begin script 44 | FIFO_NAME="factorio-server-fifo" 45 | LOG_FILE="factorio-server.log" 46 | COMMAND="$1" 47 | 48 | # Generate two unique log markers 49 | TIMESTAMP=$(date +"%s") 50 | START="FACTORIO_INIT_CMD_${TIMESTAMP}_START" 51 | END="FACTORIO_INIT_CMD_${TIMESTAMP}_END" 52 | 53 | # Whisper that unknown player to place start marker in log 54 | echo "/w ${START}" > "${FIFO_NAME}" 55 | # Run the actual command 56 | echo "${COMMAND}" > "${FIFO_NAME}" 57 | # Whisper that unknown player again to place end marker in log after the command terminated 58 | echo "/w ${END}" > "${FIFO_NAME}" 59 | 60 | # search for the start marker in the log file, then follow and print the log output in real time until the end marker is found 61 | sleep 0.5 62 | CMD_RESPONSE=$(awk "/Player ${START} doesn't exist./{flag=1;next}/Player ${END} doesn't exist./{exit}flag" < "${LOG_FILE}") 63 | echo ${CMD_RESPONSE} 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-factorio 2 | 3 | A small, simple containerized Factorio server that supports interactive and non-interactive server commands. Suitable for local and hosted servers. Simple, low-configuration and highly customizable. 4 | 5 | ## Building the image 6 | ```bash 7 | docker build -t docker-factorio . 8 | ``` 9 | 10 | ## Running 11 | On x86 machines: 12 | ```bash 13 | docker run --name docker-factorio -itp 34197:34197/udp 6davids/docker-factorio 14 | docker run --name docker-factorio -itdp 34197:34197/udp 6davids/docker-factorio # runs detached 15 | ``` 16 | 17 | On m1 Macs: 18 | ```bash 19 | docker run --name docker-factorio -itp 34197:34197/udp --platform linux/amd64 6davids/docker-factorio # ctrl-c kills server 20 | docker run --name docker-factorio -itdp 34197:34197/udp --platform linux/amd64 6davids/docker-factorio # runs detached 21 | ``` 22 | 23 | ## Server Commands 24 | 25 | Interactive: 26 | ```bash 27 | david@focus docker-factorio % # Hop into the container 28 | david@focus docker-factorio % docker exec -it docker-factorio bash 29 | root@docker-factorio:/app 30 | root@docker-factorio:/app # Start Interactive Script 31 | root@docker-factorio:/app ./interact.sh 32 | 250.157 Info ServerMultiplayerManager.cpp:943: updateTick(15117) received stateChanged peerID(1) oldState(ConnectedDownloadingMap) newState(ConnectedLoadingMap) 33 | 250.341 Info ServerMultiplayerManager.cpp:943: updateTick(15127) received stateChanged peerID(1) oldState(ConnectedLoadingMap) newState(TryingToCatchUp) 34 | 250.342 Info ServerMultiplayerManager.cpp:943: updateTick(15127) received stateChanged peerID(1) oldState(TryingToCatchUp) newState(WaitingForCommandToStartSendingTickClosures) 35 | 250.355 Info GameActionHandler.cpp:4996: UpdateTick (15127) processed PlayerJoinGame peerID(1) playerIndex(0) mode(connect) 36 | 250.407 Info ServerMultiplayerManager.cpp:943: updateTick(15131) received stateChanged peerID(1) oldState(WaitingForCommandToStartSendingTickClosures) newState(InGame) 37 | 2022-04-09 06:08:43 [JOIN] david joined the game 38 | sup 39 | 2022-04-09 06:08:51 [CHAT] : sup 40 | 2022-04-09 06:09:46 [CHAT] david: lol 41 | /kick david lol 42 | ``` 43 | 44 | Non-Interactive (for scripts or one-offs): 45 | ```bash 46 | david@focus docker-factorio % # Hop into the container 47 | david@focus docker-factorio % docker exec -it docker-factorio bash 48 | root@docker-factorio:/app # Send commands to the server 49 | root@docker-factorio:/app ./run-command.sh "sup" 50 | 2022-04-09 17:03:11 [CHAT] : sup 51 | root@docker-factorio:/app ./run-command.sh "/kick david lol" 52 | 2022-04-09 17:03:24 [KICK] david was kicked by . Reason: lol. 1352.490 Info ServerMultiplayerManager.cpp:1061: Disconnect notification for peer (1) 53 | ``` 54 | 55 | Or, if you don't need to see the command output: 56 | ```bash 57 | david@focus docker-factorio % # Hop into the container 58 | david@focus docker-factorio % docker exec -it docker-factorio bash 59 | root@docker-factorio:/app # Send commands to the server 60 | root@docker-factorio:/app echo "sup" > ./factorio-server-fifo 61 | root@docker-factorio:/app echo "/kick david lol" > ./factorio-server-fifo 62 | ``` 63 | 64 | ## Starting, Stopping, Restarting the Server 65 | 66 | The image comes with a few scripts to help managing server execution. 67 | 68 | ```bash 69 | root@docker-factorio:/app # Hop into the container 70 | david@focus docker-factorio % docker exec -it docker-factorio bash 71 | root@docker-factorio:/app # Stop the factorio server 72 | root@docker-factorio:/app ./stop-server.sh 73 | Stopping Factorio Server... 74 | Done! 75 | root@docker-factorio:/app # The server starts on it's own, but you can start it again after stopping it. 76 | root@docker-factorio:/app ./start-server.sh 77 | Starting Factorio Server... 78 | Done! 79 | root@docker-factorio:/app # Cycle the server. Useful after config changes. 80 | root@docker-factorio:/app ./restart-server.sh 81 | Stopping Factorio Server... 82 | Done! 83 | Starting Factorio Server... 84 | Done! 85 | ``` 86 | 87 | 88 | ## Saved Games 89 | 90 | By default the container creates a new game and then uses it from then on. If you'd like to use your own games, create two mounts when running the container: 91 | - `/app/factorio/saves/` 92 | - `/app/factorio/last-game.txt` 93 | 94 | Drop your save files in the `saves/` directory, and then enter the name of the save file you'd to use into `last-game.txt` 95 | 96 | Example: 97 | ```bash 98 | david@focus docker-factorio % cp my-cool-saved-game.zip ~/factorio-stuffs/saved-games/ 99 | david@focus docker-factorio % echo "my-cool-saved-game.zip" > ~/factorio-stuffs/last-game.txt 100 | david@focus docker-factorio % # Start the container detached, with ports mounted, and the two binds mentioned above 101 | david@focus docker-factorio % docker run --name docker-factorio -it \ 102 | > -d 103 | > -p 34197:34197/udp \ 104 | > --mount type=bind,source=~/factorio-stuffs/saved-games,target=/app/factorio/saves \ 105 | > --mount type=bind,source=~/factorio-stuffs/last-game.txt,target=/app/factorio/last-game.txt \ 106 | > 6davids/docker-factorio 107 | abdac7023091 108 | david@focus docker-factorio % # We're ready to go 109 | ``` 110 | 111 | ## Custom Settings 112 | 113 | Settings can be managed the same way that saved games are. Mount files in the default locations to populate the server configs. 114 | 115 | ## To-Do 116 | - Commands to start, stop, and restart the server 117 | - Load and backup savefiles from s3? 118 | - Small REST API to control the server 119 | - RCON support? 120 | - Create settings examples 121 | - Kubernetes boilerplate configs 122 | - Github action to build and push automatically with new factorio updates 123 | 124 | ## Links 125 | 126 | - Repository: https://github.com/sover02/docker-factorio 127 | - Dockerhub: https://hub.docker.com/repository/docker/6davids/docker-factorio 128 | --------------------------------------------------------------------------------